Module: GitPr::GitHub

Defined in:
lib/git_pr/github.rb

Constant Summary collapse

AUTH_KEY_NAME =
"git-merge-pull"
NETRC_KEY =
"#{AUTH_KEY_NAME}.api.github.com"
DEFAULT_REMOTE_KEY =
"pr.defaultremote"

Class Method Summary collapse

Class Method Details

.determine_project_name_from_command_line(git, project_name, default_remotes) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/git_pr/github.rb', line 83

def self.determine_project_name_from_command_line git, project_name, default_remotes
  # Figure out what GitHub project we're dealing with. First, did they pass us a name of
  # an existing remote, or did they pass a GitHub project?
  default_remote_from_gitconfig = git.config DEFAULT_REMOTE_KEY
  if project_name
    project_remote = git.remotes.find { |x| x.name == project_name }
  elsif !default_remote_from_gitconfig.empty?
    puts "Using #{DEFAULT_REMOTE_KEY} setting '#{default_remote_from_gitconfig}' from gitconfig" if $verbose
    project_remote = git.remotes.find { |x| x.name == default_remote_from_gitconfig }
    unless project_remote
      puts "The remote '#{default_remote_from_gitconfig}' doesn't exist.".red
      puts "Fix the value of '#{DEFAULT_REMOTE_KEY}' in gitconfig.".red
      exit -1
    end
  else
    project_remote = git.remotes.find { |x| default_remotes.include? x.name }
  end
  if project_remote
    # Regex comment: match the github_user/repository non-greedily (.*?), and
    # accept an optional .git at the end, but don't capture it (?:\.git).
    url_match = project_remote.url.match /^[email protected]:(.*?)(?:\.git)?$/
    unless url_match
      puts "Specified remote '#{project_remote}' is not a GitHub remote.".red
      puts "Remote URL: #{project_remote.url}".red if $verbose
      exit -1
    end
    github_project = url_match[1]
  else
    github_project = project_name
  end

  unless github_project
    puts "Unable to determine the active GitHub project.".red
    puts "For more help, run: git pr -h"
    exit -1
  end

  begin
    github_repo = Octokit.repo "#{github_project}"
  rescue
    puts "Project '#{github_project}' is not a valid GitHub project.".red
    exit -1
  end

  github_project
end

.find_or_prompt_for_pull_request(github_project, pull_request) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/git_pr/github.rb', line 143

def self.find_or_prompt_for_pull_request github_project, pull_request
  pulls = Octokit.pulls("#{github_project}").map { |p| GitPr::PullRequest.new(p) }
  unless pulls.length > 0
    puts "No open pull requests found for '#{github_project}'.".yellow
    exit
  end
  if pull_request > 0
    pull_request = pull_request
    pull = pulls.find { |p| p.number == pull_request }
    unless pull
      puts "Pull request #{pull_request} not found in project '#{github_project}'!".red
      exit -1
    end
  else
    pull = self.query_for_pull_to_merge pulls
  end
  pull
end

.initialize_octokitObject



75
76
77
78
79
80
81
# File 'lib/git_pr/github.rb', line 75

def self.initialize_octokit
  n = Netrc.read
  user, oauth_token = n[NETRC_KEY]
  Octokit.configure do |c|
    c.access_token = oauth_token
  end
end

.prompt_for_credentials(args = {}) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/git_pr/github.rb', line 25

def self.prompt_for_credentials(args = {})
  user = args[:user]
  pass = args[:pass]
  needs_otp = args[:needs_otp]
  headers = {}

  unless user
    print "Enter your github username: "
    user = STDIN.gets.chomp!
    print "Enter github password for #{user} (never stored): "
    pass = STDIN.noecho(&:gets).chomp!
    puts "\n"
  end

  if needs_otp
    print "Enter an OTP code: "
    otp = STDIN.gets.chomp!
    headers = { "X-GitHub-OTP" => "#{otp}" }
  end

  client = Octokit::Client.new :login => user, :password => pass
  begin
    hostname = `hostname`.strip!
    auth = client.create_authorization(:scopes => ["user", "repo"],
                                       :note => "#{AUTH_KEY_NAME} (#{hostname})",
                                       :fingerprint => "#{hostname} #{Time.now}",
                                       :headers => headers)
  rescue Octokit::Unauthorized
    puts "Invalid username or password."
    return false
  rescue Octokit::OneTimePasswordRequired
    # Clients that receive OTP codes via SMS won't get one when we do a get request to client.authorizations
    # We have to make a post to the authorizations endpoint to trigger the sending of the SMS code.
    # https://github.com/github/hub/commit/3d29989
    begin
      result = client.post "authorizations"
    rescue Octokit::OneTimePasswordRequired
    end

    # Come back through this method, prompting for an OTP
    return prompt_for_credentials :user => user, :pass => pass, :needs_otp => true
  end

  n = Netrc.read
  n[NETRC_KEY] = user, auth[:token]
  n.save

  return true
end

.query_for_pull_to_merge(pulls) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/git_pr/github.rb', line 130

def self.query_for_pull_to_merge(pulls)
  puts
  pull_to_merge = nil
  choose do |menu|
    menu.prompt = "Select PR to merge: "
    pulls.each do |pull|
      menu.choice(pull.summary) { pull_to_merge = pull }
    end
    menu.choice(:Quit, "Exit program.") { exit }
  end
  pull_to_merge
end

.test_credentialsObject



13
14
15
16
17
18
19
20
21
22
23
# File 'lib/git_pr/github.rb', line 13

def self.test_credentials
  n = Netrc.read
  user, oauth_token = n[NETRC_KEY]
  client = Octokit::Client.new :access_token => oauth_token
  begin
    client.user
  rescue
    return false
  end
  return true
end