git-review 2.0.0.alpha → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,289 +0,0 @@
1
- require 'net/http'
2
- require 'net/https'
3
- # Used to handle json data
4
- require 'yajl'
5
- # Required to hide password
6
- require 'io/console'
7
- # Required by yajl for decoding
8
- require 'stringio'
9
- # Used to retrieve hostname
10
- require 'socket'
11
-
12
-
13
- module GitReview
14
-
15
- class Github
16
-
17
- include ::GitReview::Internals
18
-
19
- attr_reader :github
20
- attr_accessor :source_repo
21
-
22
- # acts like a singleton class but it's actually not
23
- # use ::GitReview::Github.instance everywhere except in tests
24
- def self.instance
25
- @instance ||= new
26
- end
27
-
28
- def initialize
29
- configure_github_access
30
- end
31
-
32
- # setup connection with Github via OAuth
33
- # @return [String] the username logged in
34
- def configure_github_access
35
- settings = ::GitReview::Settings.instance
36
- if settings.oauth_token && settings.username
37
- @github = Octokit::Client.new(
38
- :login => settings.username,
39
- :access_token => settings.oauth_token,
40
- :auto_traversal => true
41
- )
42
- @github.login
43
- else
44
- configure_oauth
45
- configure_github_access
46
- end
47
- end
48
-
49
- # @return [Boolean, Hash] the specified request if exists, otherwise false.
50
- # Instead of true, the request itself is returned, so another round-trip
51
- # of pull_request can be avoided.
52
- def request_exists?(number, state='open')
53
- return false if number.nil?
54
- request = @github.pull_request(source_repo, number)
55
- request.state == state ? request : false
56
- rescue Octokit::NotFound
57
- false
58
- end
59
-
60
- def request_exists_for_branch?(upstream=false, branch=local.source_branch)
61
- target_repo = local.target_repo(upstream)
62
- @github.pull_requests(target_repo).any? { |r|
63
- r.head.ref == branch
64
- }
65
- end
66
-
67
- # an alias to pull_requests
68
- def current_requests(repo=source_repo)
69
- @github.pull_requests(repo)
70
- end
71
-
72
- # a more detailed collection of requests
73
- def current_requests_full(repo=source_repo)
74
- @github.pull_requests(repo).collect { |request|
75
- @github.pull_request(repo, request.number)
76
- }
77
- end
78
-
79
- def update
80
- git_call('fetch origin')
81
- end
82
-
83
- # @return [Array(String, String)] user and repo name from local git config
84
- def repo_info_from_config
85
- git_config = local.config
86
- url = git_config['remote.origin.url']
87
- raise ::GitReview::InvalidGitRepositoryError if url.nil?
88
-
89
- user, project = github_url_matching(url)
90
- # if there are no results yet, look for 'insteadof' substitutions
91
- # in URL and try again
92
- unless user && project
93
- insteadof_url, true_url = github_insteadof_matching(git_config, url)
94
- if insteadof_url and true_url
95
- url = url.sub(insteadof_url, true_url)
96
- user, project = github_url_matching(url)
97
- end
98
- end
99
- [user, project]
100
- end
101
-
102
- # @return [String] the source repo
103
- def source_repo
104
- # cache source_repo
105
- if @source_repo
106
- @source_repo
107
- else
108
- user, repo = repo_info_from_config
109
- @source_repo = "#{user}/#{repo}" if user && repo
110
- end
111
- end
112
-
113
- def commit_discussion(number)
114
- pull_commits = @github.pull_commits(source_repo, number)
115
- repo = @github.pull_request(source_repo, number).head.repo.full_name
116
- discussion = ["Commits on pull request:\n\n"]
117
- discussion += pull_commits.collect { |commit|
118
- # commit message
119
- name = commit.committer.login
120
- output = "\e[35m#{name}\e[m "
121
- output << "committed \e[36m#{commit.sha[0..6]}\e[m "
122
- output << "on #{format_time(commit.commit.committer.date)}"
123
- output << ":\n#{''.rjust(output.length + 1, "-")}\n"
124
- output << "#{commit.commit.message}"
125
- output << "\n\n"
126
- result = [output]
127
-
128
- # comments on commit
129
- comments = @github.commit_comments(repo, commit.sha)
130
- result + comments.collect { |comment|
131
- name = comment.user.login
132
- output = "\e[35m#{name}\e[m "
133
- output << "added a comment to \e[36m#{commit.sha[0..6]}\e[m"
134
- output << " on #{format_time(comment.created_at)}"
135
- unless comment.created_at == comment.updated_at
136
- output << " (updated on #{format_time(comment.updated_at)})"
137
- end
138
- output << ":\n#{''.rjust(output.length + 1, "-")}\n"
139
- output << comment.body
140
- output << "\n\n"
141
- }
142
- }
143
- discussion.compact.flatten unless discussion.empty?
144
- end
145
-
146
- def issue_discussion(number)
147
- comments = @github.issue_comments(source_repo, number)
148
- discussion = ["\nComments on pull request:\n\n"]
149
- discussion += comments.collect { |comment|
150
- name = comment.user.login
151
- output = "\e[35m#{name}\e[m "
152
- output << "added a comment to \e[36m#{comment.id}\e[m"
153
- output << " on #{format_time(comment.created_at)}"
154
- unless comment.created_at == comment.updated_at
155
- output << " (updated on #{format_time(comment.updated_at)})"
156
- end
157
- output << ":\n#{''.rjust(output.length + 1, "-")}\n"
158
- output << comment.body
159
- output << "\n\n"
160
- }
161
- discussion.compact.flatten unless discussion.empty?
162
- end
163
-
164
- # show discussion for a request
165
- def discussion(number)
166
- commit_discussion(number) +
167
- issue_discussion(number)
168
- end
169
-
170
- # show latest pull request number
171
- def latest_request_number(repo=source_repo)
172
- current_requests(repo).collect(&:number).sort.last.to_i
173
- end
174
-
175
- # get the number of the request that matches the title
176
- def request_number_by_title(title, repo=source_repo)
177
- request = current_requests(repo).find { |r| r.title == title }
178
- request.number if request
179
- end
180
-
181
- # delegate methods that interact with Github to Octokit client
182
- def method_missing(method, *args)
183
- if @github.respond_to?(method)
184
- @github.send(method, *args)
185
- else
186
- super
187
- end
188
- end
189
-
190
- def respond_to?(method)
191
- @github.respond_to?(method) || super
192
- end
193
-
194
- private
195
-
196
- def configure_oauth
197
- begin
198
- prepare_username_and_password
199
- prepare_description
200
- authorize
201
- rescue ::GitReview::AuthenticationError => e
202
- warn e.message
203
- rescue ::GitReview::UnprocessableState => e
204
- warn e.message
205
- exit 1
206
- end
207
- end
208
-
209
- def prepare_username_and_password
210
- puts "Requesting a OAuth token for git-review."
211
- puts "This procedure will grant access to your public and private "\
212
- "repositories."
213
- puts "You can revoke this authorization by visiting the following page: "\
214
- "https://github.com/settings/applications"
215
- print "Please enter your GitHub's username: "
216
- @username = STDIN.gets.chomp
217
- print "Please enter your GitHub's password (it won't be stored anywhere): "
218
- @password = STDIN.noecho(&:gets).chomp
219
- print "\n"
220
- end
221
-
222
- def prepare_description(chosen_description=nil)
223
- if chosen_description
224
- @description = chosen_description
225
- else
226
- @description = "git-review - #{Socket.gethostname}"
227
- puts "Please enter a description to associate to this token, it will "\
228
- "make easier to find it inside of GitHub's application page."
229
- puts "Press enter to accept the proposed description"
230
- print "Description [#{@description}]:"
231
- user_description = STDIN.gets.chomp
232
- @description = user_description.empty? ? @description : user_description
233
- end
234
- end
235
-
236
- def authorize
237
- uri = URI('https://api.github.com/authorizations')
238
- http = Net::HTTP.new(uri.host, uri.port)
239
- http.use_ssl = true
240
- req = Net::HTTP::Post.new(uri.request_uri)
241
- req.basic_auth(@username, @password)
242
- req.body = Yajl::Encoder.encode(
243
- {
244
- :scopes => %w(repo),
245
- :note => @description
246
- }
247
- )
248
- response = http.request(req)
249
- if response.code == '201'
250
- parser_response = Yajl::Parser.parse(response.body)
251
- save_oauth_token(parser_response['token'])
252
- elsif response.code == '401'
253
- raise ::GitReview::AuthenticationError
254
- else
255
- raise ::GitReview::UnprocessableState, response.body
256
- end
257
- end
258
-
259
- def save_oauth_token(token)
260
- settings = ::GitReview::Settings.instance
261
- settings.oauth_token = token
262
- settings.username = @username
263
- settings.save!
264
- puts "OAuth token successfully created.\n"
265
- end
266
-
267
- # extract user and project name from GitHub URL.
268
- def github_url_matching(url)
269
- matches = /github\.com.(.*?)\/(.*)/.match(url)
270
- matches ? [matches[1], matches[2].sub(/\.git\z/, '')] : [nil, nil]
271
- end
272
-
273
- # look for 'insteadof' substitutions in URL.
274
- def github_insteadof_matching(config, url)
275
- first_match = config.keys.collect { |key|
276
- [config[key], /url\.(.*github\.com.*)\.insteadof/.match(key)]
277
- }.find { |insteadof_url, true_url|
278
- url.index(insteadof_url) and true_url != nil
279
- }
280
- first_match ? [first_match[0], first_match[1][1]] : [nil, nil]
281
- end
282
-
283
- def local
284
- @local ||= ::GitReview::Local.instance
285
- end
286
-
287
- end
288
-
289
- end
@@ -1,47 +0,0 @@
1
- module GitReview
2
-
3
- module Internals
4
-
5
- private
6
-
7
- # System call to 'git'.
8
- def git_call(command, verbose = debug_mode, enforce_success = false)
9
- if verbose
10
- puts
11
- puts " git #{command}"
12
- puts
13
- end
14
- output = `git #{command}`
15
- puts output if verbose and not output.empty?
16
- # If we need sth. to succeed, but it doesn't, then stop right there.
17
- if enforce_success and not last_command_successful?
18
- puts output unless output.empty?
19
- raise ::GitReview::UnprocessableState
20
- end
21
- output
22
- end
23
-
24
- # @return [Boolean] whether the last issued system call was successful
25
- def last_command_successful?
26
- $?.exitstatus == 0
27
- end
28
-
29
- def debug_mode
30
- ::GitReview::Settings.instance.review_mode == 'debug'
31
- end
32
-
33
- # display helper to make output more configurable
34
- def format_text(info, size)
35
- info.to_s.gsub("\n", ' ')[0, size-1].ljust(size)
36
- end
37
-
38
- # display helper to unify time output
39
- def format_time(time)
40
- time = Time.parse(time) if time.is_a?(String)
41
- time.strftime('%d-%b-%y')
42
- end
43
-
44
- end
45
-
46
- end
47
-