octopolo 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +21 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +3 -0
- data/Guardfile +5 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +55 -0
- data/Rakefile +38 -0
- data/bash_completion.sh +13 -0
- data/bin/octopolo +21 -0
- data/bin/op +21 -0
- data/lib/octopolo.rb +15 -0
- data/lib/octopolo/changelog.rb +27 -0
- data/lib/octopolo/cli.rb +210 -0
- data/lib/octopolo/commands/accept_pull.rb +8 -0
- data/lib/octopolo/commands/compare_release.rb +9 -0
- data/lib/octopolo/commands/deployable.rb +8 -0
- data/lib/octopolo/commands/github_auth.rb +5 -0
- data/lib/octopolo/commands/new_branch.rb +9 -0
- data/lib/octopolo/commands/new_deployable.rb +8 -0
- data/lib/octopolo/commands/new_staging.rb +8 -0
- data/lib/octopolo/commands/octopolo_setup.rb +5 -0
- data/lib/octopolo/commands/pivotal_auth.rb +5 -0
- data/lib/octopolo/commands/pull_request.rb +13 -0
- data/lib/octopolo/commands/signoff.rb +10 -0
- data/lib/octopolo/commands/stage_up.rb +8 -0
- data/lib/octopolo/commands/stale_branches.rb +11 -0
- data/lib/octopolo/commands/sync_branch.rb +11 -0
- data/lib/octopolo/commands/tag_release.rb +13 -0
- data/lib/octopolo/config.rb +146 -0
- data/lib/octopolo/convenience_wrappers.rb +46 -0
- data/lib/octopolo/dated_branch_creator.rb +81 -0
- data/lib/octopolo/git.rb +262 -0
- data/lib/octopolo/github.rb +95 -0
- data/lib/octopolo/github/commit.rb +45 -0
- data/lib/octopolo/github/pull_request.rb +126 -0
- data/lib/octopolo/github/pull_request_creator.rb +127 -0
- data/lib/octopolo/github/user.rb +40 -0
- data/lib/octopolo/jira/story_commenter.rb +26 -0
- data/lib/octopolo/pivotal.rb +44 -0
- data/lib/octopolo/pivotal/story_commenter.rb +19 -0
- data/lib/octopolo/pull_request_merger.rb +99 -0
- data/lib/octopolo/renderer.rb +37 -0
- data/lib/octopolo/reports.rb +18 -0
- data/lib/octopolo/scripts.rb +23 -0
- data/lib/octopolo/scripts/accept_pull.rb +67 -0
- data/lib/octopolo/scripts/compare_release.rb +52 -0
- data/lib/octopolo/scripts/deployable.rb +27 -0
- data/lib/octopolo/scripts/github_auth.rb +87 -0
- data/lib/octopolo/scripts/new_branch.rb +34 -0
- data/lib/octopolo/scripts/new_deployable.rb +14 -0
- data/lib/octopolo/scripts/new_staging.rb +15 -0
- data/lib/octopolo/scripts/octopolo_setup.rb +55 -0
- data/lib/octopolo/scripts/pivotal_auth.rb +44 -0
- data/lib/octopolo/scripts/pull_request.rb +127 -0
- data/lib/octopolo/scripts/signoff.rb +85 -0
- data/lib/octopolo/scripts/stage_up.rb +26 -0
- data/lib/octopolo/scripts/stale_branches.rb +54 -0
- data/lib/octopolo/scripts/sync_branch.rb +37 -0
- data/lib/octopolo/scripts/tag_release.rb +70 -0
- data/lib/octopolo/templates/pull_request_body.erb +24 -0
- data/lib/octopolo/user_config.rb +112 -0
- data/lib/octopolo/version.rb +3 -0
- data/lib/octopolo/week.rb +130 -0
- data/octopolo.gemspec +31 -0
- data/spec/.DS_Store +0 -0
- data/spec/octopolo/cli_spec.rb +310 -0
- data/spec/octopolo/config_spec.rb +344 -0
- data/spec/octopolo/convenience_wrappers_spec.rb +80 -0
- data/spec/octopolo/dated_branch_creator_spec.rb +143 -0
- data/spec/octopolo/git_spec.rb +419 -0
- data/spec/octopolo/github/commit_spec.rb +59 -0
- data/spec/octopolo/github/pull_request_creator_spec.rb +174 -0
- data/spec/octopolo/github/pull_request_spec.rb +291 -0
- data/spec/octopolo/github/user_spec.rb +65 -0
- data/spec/octopolo/github_spec.rb +169 -0
- data/spec/octopolo/jira/stor_commenter_spec.rb +30 -0
- data/spec/octopolo/pivotal/story_commenter_spec.rb +34 -0
- data/spec/octopolo/pivotal_spec.rb +61 -0
- data/spec/octopolo/pull_request_merger_spec.rb +144 -0
- data/spec/octopolo/renderer_spec.rb +35 -0
- data/spec/octopolo/scripts/accept_pull_spec.rb +76 -0
- data/spec/octopolo/scripts/compare_release_spec.rb +115 -0
- data/spec/octopolo/scripts/deployable_spec.rb +52 -0
- data/spec/octopolo/scripts/github_auth_spec.rb +156 -0
- data/spec/octopolo/scripts/new_branch_spec.rb +41 -0
- data/spec/octopolo/scripts/new_deployable_spec.rb +18 -0
- data/spec/octopolo/scripts/new_staging_spec.rb +18 -0
- data/spec/octopolo/scripts/octopolo_setup_spec.rb +120 -0
- data/spec/octopolo/scripts/pivotal_auth_spec.rb +77 -0
- data/spec/octopolo/scripts/pull_request_spec.rb +217 -0
- data/spec/octopolo/scripts/signoff_spec.rb +139 -0
- data/spec/octopolo/scripts/stage_up_spec.rb +52 -0
- data/spec/octopolo/scripts/stale_branches_spec.rb +81 -0
- data/spec/octopolo/scripts/sync_branch_spec.rb +57 -0
- data/spec/octopolo/scripts/tag_release_spec.rb +108 -0
- data/spec/octopolo/user_config_spec.rb +167 -0
- data/spec/octopolo_spec.rb +7 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/engine_yard.cache +0 -0
- data/spec/support/sample_octopolo.yml +2 -0
- data/spec/support/sample_user.yml +2 -0
- data/templates/lib.erb +23 -0
- data/templates/script.erb +7 -0
- data/templates/spec.erb +29 -0
- metadata +344 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
require "pivotal-tracker" # this is the gem we're currently using
|
2
|
+
require_relative "scripts/pivotal_auth"
|
3
|
+
|
4
|
+
module Octopolo
|
5
|
+
module Pivotal
|
6
|
+
# NOTE Should probably extract out to
|
7
|
+
class Client
|
8
|
+
include UserConfigWrapper
|
9
|
+
# Public: Initialize a new instance of Pivotal::Client wrapper class
|
10
|
+
def initialize
|
11
|
+
# no idea why this is off by default (as of 2013-04-18)
|
12
|
+
::PivotalTracker::Client.use_ssl = true
|
13
|
+
begin
|
14
|
+
::PivotalTracker::Client.token = user_config.pivotal_token
|
15
|
+
rescue UserConfig::MissingPivotalAuth
|
16
|
+
Scripts::PivotalAuth.run
|
17
|
+
::PivotalTracker::Client.token = UserConfig.parse.pivotal_token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Fetch an API token for the given authentication
|
22
|
+
#
|
23
|
+
# email - a String containing the email address used to log in
|
24
|
+
# password - a String containing the password
|
25
|
+
#
|
26
|
+
# Returns a String or raises BadCredentials
|
27
|
+
def self.fetch_token(email, password)
|
28
|
+
::PivotalTracker::Client.token(email, password)
|
29
|
+
rescue RestClient::Unauthorized
|
30
|
+
raise BadCredentials, "No token received from Pivotal Tracker. Please check your credentials and try again."
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_story(story_id)
|
34
|
+
@projects = PivotalTracker::Project.all
|
35
|
+
@projects.map{ |project| project.stories.find(story_id) }.compact.first || raise(StoryNotFound, "No Story was found with that ID in your Projects")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# the credentials that you've entered are wrong
|
40
|
+
BadCredentials = Class.new(StandardError)
|
41
|
+
# 404 from the PT api
|
42
|
+
StoryNotFound = Class.new(StandardError)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative '../pivotal'
|
2
|
+
|
3
|
+
module Octopolo
|
4
|
+
module Pivotal
|
5
|
+
class StoryCommenter
|
6
|
+
attr_accessor :story
|
7
|
+
attr_accessor :comment
|
8
|
+
|
9
|
+
def initialize(story_id, comment)
|
10
|
+
self.story = Pivotal::Client.new.find_story(story_id)
|
11
|
+
self.comment = comment
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform
|
15
|
+
story.notes.new(:owner => story, :text => comment).create
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require_relative "scripts"
|
2
|
+
require_relative "git"
|
3
|
+
require_relative "github/pull_request"
|
4
|
+
require_relative "dated_branch_creator"
|
5
|
+
|
6
|
+
module Octopolo
|
7
|
+
class PullRequestMerger
|
8
|
+
include ConfigWrapper
|
9
|
+
include CLIWrapper
|
10
|
+
include GitWrapper
|
11
|
+
|
12
|
+
attr_accessor :branch_type, :pull_request_id, :options, :pull_request
|
13
|
+
|
14
|
+
# Public: Initialize a new instance of DatedBranchCreator
|
15
|
+
#
|
16
|
+
# branch_type - Name of the type of branch (e.g., staging or deployable)
|
17
|
+
# pull_request_id - The pull request id to use for the merge
|
18
|
+
# options - hash of options
|
19
|
+
# - post_comment - whether or not to post a comment on the pull-request
|
20
|
+
def initialize(branch_type, pull_request_id, options={})
|
21
|
+
self.branch_type = branch_type
|
22
|
+
self.pull_request_id = pull_request_id
|
23
|
+
self.options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: Create a new branch of the given type for today's date
|
27
|
+
#
|
28
|
+
# branch_type - Name of the type of branch (e.g., staging or deployable)
|
29
|
+
# post_comment - Whether or not to comment on the pull-request
|
30
|
+
#
|
31
|
+
# Returns a DatedBranchCreator
|
32
|
+
def self.perform(branch_type, pull_request_id, options={})
|
33
|
+
new(branch_type, pull_request_id, options).tap do |creator|
|
34
|
+
creator.perform
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Create the branch and handle related processing
|
39
|
+
def perform
|
40
|
+
git.if_clean do
|
41
|
+
check_out_branch
|
42
|
+
merge_pull_request
|
43
|
+
comment_about_merge
|
44
|
+
end
|
45
|
+
rescue GitHub::PullRequest::NotFound
|
46
|
+
cli.say "Unable to find pull request #{pull_request_id}. Please retry with a valid ID."
|
47
|
+
rescue Git::MergeFailed
|
48
|
+
cli.say "Merge failed. Please identify the source of this merge conflict resolve this conflict in your pull request's branch. NOTE: Merge conflicts resolved in the #{branch_type} branch are NOT used when deploying."
|
49
|
+
rescue Git::CheckoutFailed
|
50
|
+
cli.say "Checkout of #{branch_to_merge_into} failed. Please contact Infrastructure to determine the cause."
|
51
|
+
rescue GitHub::PullRequest::CommentFailed
|
52
|
+
cli.say "Unable to write comment. Please navigate to #{pull_request.url} and add the comment, '#{comment_body}'"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: Check out the branch
|
56
|
+
def check_out_branch
|
57
|
+
git.check_out branch_to_merge_into
|
58
|
+
rescue Git::NoBranchOfType
|
59
|
+
cli.say "No #{branch_type} branch available. Creating one now."
|
60
|
+
git.check_out DatedBranchCreator.perform(branch_type).branch_name
|
61
|
+
end
|
62
|
+
|
63
|
+
# Public: Merge the pull request's branch into the checked-out branch
|
64
|
+
def merge_pull_request
|
65
|
+
git.merge pull_request.branch
|
66
|
+
end
|
67
|
+
|
68
|
+
# Public: Comment that the pull request was merged into the branch
|
69
|
+
def comment_about_merge
|
70
|
+
pull_request.write_comment comment_body
|
71
|
+
end
|
72
|
+
|
73
|
+
# Public: The content of the comment to post when merged
|
74
|
+
#
|
75
|
+
# Returns a String
|
76
|
+
def comment_body
|
77
|
+
body = "Merged into #{branch_to_merge_into}."
|
78
|
+
if options[:user_notifications]
|
79
|
+
body << " /cc #{options[:user_notifications].map {|name| "@#{name}"}.join(' ')}"
|
80
|
+
end
|
81
|
+
body
|
82
|
+
end
|
83
|
+
|
84
|
+
# Public: Find the pull request
|
85
|
+
#
|
86
|
+
# Returns a GitHub::PullRequest
|
87
|
+
def pull_request
|
88
|
+
@pull_request ||= GitHub::PullRequest.new config.github_repo, pull_request_id
|
89
|
+
end
|
90
|
+
|
91
|
+
# Public: Find the branch
|
92
|
+
#
|
93
|
+
# Returns a String
|
94
|
+
def branch_to_merge_into
|
95
|
+
@branch_to_merge_into ||= git.latest_branch_for(branch_type)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "ostruct"
|
3
|
+
|
4
|
+
module Octopolo
|
5
|
+
class Renderer
|
6
|
+
# Constants for the template file names
|
7
|
+
PULL_REQUEST_BODY = "pull_request_body"
|
8
|
+
|
9
|
+
# Public: Render a given ERB template
|
10
|
+
#
|
11
|
+
# template - A String contianing the name of the ERB template
|
12
|
+
# locals - A Hash containing variables to render. The keys must match the variable names in the template
|
13
|
+
#
|
14
|
+
# Lifted from [Stack Overflow](http://stackoverflow.com/questions/8954706/render-an-erb-template-with-values-from-a-hash)
|
15
|
+
#
|
16
|
+
# Example
|
17
|
+
#
|
18
|
+
# render "first_last_name_template", {first: "Bob", last: "Person"}
|
19
|
+
# # => "Bob Person"
|
20
|
+
#
|
21
|
+
# Returns a String containing the rendered template
|
22
|
+
def self.render template, locals
|
23
|
+
# template_string, safe_mode = 0, "-" to trim whitespace in ERB tags ending -%> (like Rails)
|
24
|
+
ERB.new(contents_of(template), 0, "-").result(OpenStruct.new(locals).instance_eval { binding })
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: The contents of the named template
|
28
|
+
def self.contents_of template
|
29
|
+
File.read File.join(template_base_path, "#{template}.erb")
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Path to the directory containing the templates
|
33
|
+
def self.template_base_path
|
34
|
+
File.expand_path(File.join(__FILE__, "../templates"))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "csv"
|
2
|
+
|
3
|
+
module Octopolo
|
4
|
+
module Reports
|
5
|
+
# Public: Write the report data to the given file name
|
6
|
+
#
|
7
|
+
# data - Array of report data, each element being a line of the CSV
|
8
|
+
# filename - The name of the file to write to
|
9
|
+
def self.write_csv data, filename
|
10
|
+
CSV.open filename, "w" do |file|
|
11
|
+
data.each do |line|
|
12
|
+
file.puts line
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative "git"
|
2
|
+
|
3
|
+
module Octopolo
|
4
|
+
module Scripts
|
5
|
+
module Base
|
6
|
+
def self.included(klass)
|
7
|
+
class << klass
|
8
|
+
attr_accessor :config
|
9
|
+
attr_accessor :cli
|
10
|
+
end
|
11
|
+
|
12
|
+
klass.config = Octopolo.config
|
13
|
+
klass.cli = Octopolo::CLI
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Mostly used for tests
|
20
|
+
unless defined?(GLI)
|
21
|
+
require 'gli'
|
22
|
+
include GLI::App
|
23
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative "../git"
|
2
|
+
require_relative "../github"
|
3
|
+
require_relative "../github/pull_request"
|
4
|
+
require_relative "../scripts"
|
5
|
+
|
6
|
+
module Octopolo
|
7
|
+
module Scripts
|
8
|
+
class AcceptPull
|
9
|
+
include Base
|
10
|
+
include GitWrapper
|
11
|
+
include ConfigWrapper
|
12
|
+
include CLIWrapper
|
13
|
+
|
14
|
+
attr_accessor :pull_request_id
|
15
|
+
|
16
|
+
def self.execute(pull_request_id)
|
17
|
+
pull_request_id ||= Integer(cli.prompt "Pull Request ID: ")
|
18
|
+
new(pull_request_id).execute
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(pull_request_id)
|
22
|
+
@pull_request_id = pull_request_id
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Perform the script
|
26
|
+
def execute
|
27
|
+
GitHub.connect do
|
28
|
+
pull_request = GitHub::PullRequest.new(config.github_repo, pull_request_id)
|
29
|
+
merge pull_request
|
30
|
+
update_changelog pull_request
|
31
|
+
end
|
32
|
+
rescue GitHub::PullRequest::NotFound
|
33
|
+
cli.say "Unable to find a pull request #{pull_request_id} for #{config.github_repo}. Please verify."
|
34
|
+
end
|
35
|
+
|
36
|
+
def merge pull_request
|
37
|
+
Git.fetch
|
38
|
+
if pull_request.mergeable?
|
39
|
+
cli.perform "git merge --no-ff origin/#{pull_request.branch} -m \"Merge pull request ##{pull_request_id} from origin/#{pull_request.branch}\""
|
40
|
+
else
|
41
|
+
cli.say "There is a merge conflict with this branch and #{config.deploy_branch}."
|
42
|
+
cli.say "Please update this branch with #{config.deploy_branch} or perform the merge manually and fix any conflicts"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def changelog
|
47
|
+
@changelog ||= Changelog.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_changelog pull_request
|
51
|
+
title = pull_request.title
|
52
|
+
authors = pull_request.author_names
|
53
|
+
commenters = pull_request.commenter_names
|
54
|
+
url = pull_request.url
|
55
|
+
|
56
|
+
changelog.open do |log|
|
57
|
+
log.puts "* #{title}"
|
58
|
+
log.puts
|
59
|
+
log.puts " > #{authors.join(", ")}: #{commenters.join(', ')}: #{url}"
|
60
|
+
log.puts
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative "../scripts"
|
2
|
+
|
3
|
+
module Octopolo
|
4
|
+
module Scripts
|
5
|
+
class CompareRelease
|
6
|
+
include Base
|
7
|
+
include GitWrapper
|
8
|
+
include ConfigWrapper
|
9
|
+
include CLIWrapper
|
10
|
+
|
11
|
+
attr_accessor :start
|
12
|
+
attr_accessor :stop
|
13
|
+
|
14
|
+
def self.execute(start, stop)
|
15
|
+
new(start, stop).execute
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(start=nil, stop=nil)
|
19
|
+
@start = start
|
20
|
+
@stop = stop
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute
|
24
|
+
ask_starting_tag
|
25
|
+
ask_stopping_tag
|
26
|
+
open_compare_page
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: Ask, if not already set, which tag to start with
|
30
|
+
def ask_starting_tag
|
31
|
+
self.start ||= cli.ask("Start with which tag?", git.recent_release_tags)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: Ask, if not already set, which tag to end with
|
35
|
+
def ask_stopping_tag
|
36
|
+
self.stop ||= cli.ask("Compare from #{start} to which tag?", git.recent_release_tags)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Open the GitHub compare URL for the starting and ending branches
|
40
|
+
def open_compare_page
|
41
|
+
cli.copy_to_clipboard compare_url
|
42
|
+
cli.open compare_url
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: The GitHub compare URL for the selected tags
|
46
|
+
def compare_url
|
47
|
+
"https://github.com/#{config.github_repo}/compare/#{start}...#{stop}?w=1"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative "../scripts"
|
2
|
+
require_relative "../pull_request_merger"
|
3
|
+
|
4
|
+
module Octopolo
|
5
|
+
module Scripts
|
6
|
+
class Deployable
|
7
|
+
include CLIWrapper
|
8
|
+
include ConfigWrapper
|
9
|
+
|
10
|
+
attr_accessor :pull_request_id
|
11
|
+
|
12
|
+
def self.execute(pull_request_id=nil)
|
13
|
+
new(pull_request_id).execute
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(pull_request_id=nil)
|
17
|
+
@pull_request_id = pull_request_id
|
18
|
+
end
|
19
|
+
|
20
|
+
# Public: Perform the script
|
21
|
+
def execute
|
22
|
+
self.pull_request_id ||= cli.prompt("Pull Request ID: ")
|
23
|
+
PullRequestMerger.perform Git::DEPLOYABLE_PREFIX, Integer(@pull_request_id), :user_notifications => config.user_notifications
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "json"
|
2
|
+
require_relative "../github"
|
3
|
+
require_relative "../scripts"
|
4
|
+
|
5
|
+
module Octopolo
|
6
|
+
module Scripts
|
7
|
+
class GithubAuth
|
8
|
+
include CLIWrapper
|
9
|
+
include UserConfigWrapper
|
10
|
+
|
11
|
+
attr_accessor :username
|
12
|
+
attr_accessor :password
|
13
|
+
attr_accessor :auth_response
|
14
|
+
attr_accessor :user_defined_token
|
15
|
+
|
16
|
+
def execute
|
17
|
+
case ask_auth_method
|
18
|
+
when "Generate an API token with my credentials"
|
19
|
+
ask_credentials
|
20
|
+
request_token
|
21
|
+
when "I'll enter an access token manually"
|
22
|
+
ask_token
|
23
|
+
verify_token
|
24
|
+
end
|
25
|
+
store_token
|
26
|
+
rescue GitHub::BadCredentials => e
|
27
|
+
cli.say e.message
|
28
|
+
end
|
29
|
+
|
30
|
+
# Private: Give option to manually get token from GitHub and use it instead of using creds (For people that use 2FA)
|
31
|
+
def ask_auth_method
|
32
|
+
question = "Would you like to generate an GitHub API token with your credentials or enter one manually?\n"\
|
33
|
+
"You *must* enter a token manually if you are using GitHub's Two Factor Authentication.\n"\
|
34
|
+
"For more information, see https://help.github.com/articles/creating-an-access-token-for-command-line-use\n\n"
|
35
|
+
choices = ["Generate an API token with my credentials", "I'll enter an access token manually"]
|
36
|
+
selected = cli.ask(question, choices)
|
37
|
+
end
|
38
|
+
private :ask_auth_method
|
39
|
+
|
40
|
+
# Private: Request the user's GitHub username and password
|
41
|
+
def ask_credentials
|
42
|
+
self.username = cli.prompt "Your GitHub username: "
|
43
|
+
self.password = cli.prompt_secret "Your GitHub password (never stored): "
|
44
|
+
end
|
45
|
+
private :ask_credentials
|
46
|
+
|
47
|
+
# Private: Request an auth token from GitHub
|
48
|
+
def request_token
|
49
|
+
json = cli.perform_quietly %Q(curl -u '#{username}:#{password}' -d '{"scopes": ["repo"], "notes": "Octopolo"}' https://api.github.com/authorizations)
|
50
|
+
self.auth_response = JSON.parse json
|
51
|
+
end
|
52
|
+
private :request_token
|
53
|
+
|
54
|
+
# Private: Verify a user_defined_token with GitHub
|
55
|
+
def verify_token
|
56
|
+
json = cli.perform_quietly %Q(curl -u #{user_defined_token}:x-oauth-basic https://api.github.com/user)
|
57
|
+
self.auth_response = JSON.parse json
|
58
|
+
end
|
59
|
+
private :verify_token
|
60
|
+
|
61
|
+
# Private: Request the user to give a token to be set manually (required for 2FA)
|
62
|
+
def ask_token
|
63
|
+
self.username = cli.prompt "Your GitHub username: "
|
64
|
+
self.user_defined_token = cli.prompt_secret "Your GitHub API token: "
|
65
|
+
end
|
66
|
+
private :ask_token
|
67
|
+
|
68
|
+
# Private: Store the token recieved from GitHub
|
69
|
+
#
|
70
|
+
# If a token is present in the response, store it in the user config.
|
71
|
+
# Otherwise indicate that the authorization did not succeed.
|
72
|
+
def store_token
|
73
|
+
token = auth_response["login"] ? user_defined_token : auth_response["token"]
|
74
|
+
if token
|
75
|
+
user_config.set :github_user, username
|
76
|
+
user_config.set :github_token, token
|
77
|
+
cli.say "Successfully stored GitHub API token."
|
78
|
+
else
|
79
|
+
raise GitHub::BadCredentials, "Uh oh, your access token couldn't be generated/verified. Please check your credentials and try again."
|
80
|
+
end
|
81
|
+
end
|
82
|
+
private :store_token
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|