git-review 1.1.7 → 2.0.0.alpha

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.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Scott Chacon
1
+ Copyright (c) 2013 Dominik Bamberger
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -4,5 +4,104 @@ require 'rubygems'
4
4
  $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
5
5
 
6
6
  require 'git-review'
7
+ require 'gli'
7
8
 
8
- GitReview.new(ARGV)
9
+ include GLI::App
10
+
11
+ program_desc 'Manage review workflow for Github projects (using pull requests).'
12
+
13
+ # Pre-hook before a command is executed
14
+ pre do |global, cmd, opts, args|
15
+ github = ::GitReview::Github.instance
16
+ if github.configure_github_access && github.source_repo
17
+ github.update unless cmd == 'clean'
18
+ end
19
+ true # return true to explicitly pass precondition
20
+ end
21
+
22
+ desc 'List all pending requests'
23
+ command :list do |c|
24
+ c.switch [:r, :reverse]
25
+ c.action do |global, opts, args|
26
+ ::GitReview::Commands.list(opts[:reverse])
27
+ end
28
+ end
29
+
30
+ desc 'Show details for a single request'
31
+ command :show do |c|
32
+ c.switch [:f, :full]
33
+ c.action do |global, opts, args|
34
+ help_now!('Request number is required.') if args.empty?
35
+ ::GitReview::Commands.show(args.shift, opts[:full])
36
+ end
37
+ end
38
+
39
+ desc 'Open request in a browser window'
40
+ command :browse do |c|
41
+ c.action do |global, opts, args|
42
+ help_now!('Request number is required.') if args.empty?
43
+ ::GitReview::Commands.browse(args.shift)
44
+ end
45
+ end
46
+
47
+ desc 'Checkout a request\'s changes to local repo'
48
+ command :checkout do |c|
49
+ c.switch [:b, :branch]
50
+ c.action do |global, opts, args|
51
+ help_now!('Request number is required.') if args.empty?
52
+ ::GitReview::Commands.checkout(args.shift, opts[:branch])
53
+ end
54
+ end
55
+
56
+ desc 'Add an approvig comment to a request'
57
+ command :approve do |c|
58
+ c.action do |global, opts, args|
59
+ help_now!('Request number is required.') if args.empty?
60
+ ::GitReview::Commands.approve(args.shift)
61
+ end
62
+ end
63
+
64
+ desc 'Accept a request by merging it into master'
65
+ command :merge do |c|
66
+ c.action do |global, opts, args|
67
+ help_now!('Request number is required.') if args.empty?
68
+ ::GitReview::Commands.merge(args.shift)
69
+ end
70
+ end
71
+
72
+ desc 'Close a request'
73
+ command :close do |c|
74
+ c.action do |global, opts, args|
75
+ help_now!('Request number is required.') if args.empty?
76
+ ::GitReview::Commands.close(args.shift)
77
+ end
78
+ end
79
+
80
+ desc 'Create a new local branch for a request'
81
+ command :prepare do |c|
82
+ c.switch [:n, :new]
83
+ c.action do |global, opts, args|
84
+ ::GitReview::Commands.prepare(opts[:new], args.shift)
85
+ end
86
+ end
87
+
88
+ desc 'Create a new pull request'
89
+ command :create do |c|
90
+ c.switch [:u, :upstream]
91
+ c.action do |global, opts, args|
92
+ ::GitReview::Commands.create(opts[:upstream])
93
+ end
94
+ end
95
+
96
+ desc 'Delete a request\'s remote and local branches'
97
+ command :clean do |c|
98
+ c.switch [:f, :force]
99
+ c.switch [:a, :all]
100
+ c.action do |global, opts, args|
101
+ help_now!('Request number is required.') if args.empty? && !opts[:all]
102
+ number = args.empty? ? nil : args.shift
103
+ ::GitReview::Commands.clean(number, opts[:force], opts[:all])
104
+ end
105
+ end
106
+
107
+ exit run(ARGV)
@@ -1,634 +1,28 @@
1
- # Octokit is used to access GitHub's API.
1
+ # Provide access to GitHub's API.
2
2
  require 'octokit'
3
- # Launchy is used in 'browse' to open a browser.
3
+ # Open a browser in 'browse' command.
4
4
  require 'launchy'
5
- # Time is used to parse time strings from git back into Time objects.
5
+ # Parse time strings from git back into Time objects.
6
6
  require 'time'
7
- # tempfile is used to create a temporary file containing PR's title and body.
8
- # This file is going to be edited by the system editor.
7
+ # Use temporary files to allow editing a request's title and body.
9
8
  require 'tempfile'
10
- # This file provides the OAuthHelper module which is used to create a oauth token/
11
- require_relative 'oauth_helper'
12
- # Setting class
13
- require_relative 'settings'
14
9
 
10
+ ## Our own dependencies
15
11
 
16
- # A custom error to raise, if we know we can't go on.
17
- class UnprocessableState < StandardError
18
- end
19
-
20
-
21
- class GitReview
22
- include OAuthHelper
23
-
24
- ## COMMANDS ##
25
-
26
- # List all pending requests.
27
- def list
28
- output = @current_requests.collect do |request|
29
- details = @github.pull_request(source_repo, request.number)
30
- # Find only pending (= unmerged) requests and output summary.
31
- # Explicitly look for local changes (that GitHub does not yet know about).
32
- next if merged?(request.head.sha)
33
- line = format_text(request.number, 8)
34
- date_string = format_time(request.updated_at)
35
- line << format_text(date_string, 11)
36
- line << format_text(details.comments + details.review_comments, 10)
37
- line << format_text(request.title, 91)
38
- line
39
- end
40
- output.compact!
41
- if output.empty?
42
- puts "No pending requests for '#{source}'."
43
- else
44
- puts "Pending requests for '#{source}':"
45
- puts 'ID Updated Comments Title'
46
- output.reverse! if @args.shift == '--reverse'
47
- output.each { |line| puts line }
48
- end
49
- end
50
-
51
-
52
- # Show details for a single request.
53
- def show
54
- return unless request_exists?
55
- option = @args.shift == '--full' ? '' : '--stat '
56
- sha = @current_request['head']['sha']
57
- puts "ID : #{@current_request['number']}"
58
- puts "Label : #{@current_request['head']['label']}"
59
- puts "Updated : #{format_time(@current_request['updated_at'])}"
60
- puts "Comments : #{@current_request['comments']}"
61
- puts
62
- puts @current_request['title']
63
- puts
64
- puts @current_request['body']
65
- puts
66
- puts git_call("diff --color=always #{option}HEAD...#{sha}")
67
- puts
68
- puts "Progress :"
69
- puts
70
- discussion
71
- end
72
-
73
-
74
- # Open a browser window and review a specified request.
75
- def browse
76
- Launchy.open(@current_request['html_url']) if request_exists?
77
- end
78
-
79
-
80
- # Checkout a specified request's changes to your local repository.
81
- def checkout
82
- return unless request_exists?
83
- create_local_branch = @args.shift == '--branch' ? '' : 'origin/'
84
- puts 'Checking out changes to your local repository.'
85
- puts 'To get back to your original state, just run:'
86
- puts
87
- puts ' git checkout master'
88
- puts
89
- git_call "checkout #{create_local_branch}#{@current_request['head']['ref']}"
90
- end
91
-
92
-
93
- # Accept a specified request by merging it into master.
94
- def merge
95
- return unless request_exists?
96
- option = @args.shift
97
- unless @current_request['head']['repo']
98
- # Someone deleted the source repo.
99
- user = @current_request['head']['user']['login']
100
- url = @current_request['patch_url']
101
- puts "Sorry, #{user} deleted the source repository, git-review doesn't support this."
102
- puts 'Tell the contributor not to do this.'
103
- puts
104
- puts 'You can still manually patch your repo by running:'
105
- puts
106
- puts " curl #{url} | git am"
107
- puts
108
- return false
109
- end
110
- message = "Accept request ##{@current_request['number']} and merge changes into \"#{target}\""
111
- exec_cmd = "merge #{option} -m '#{message}' #{@current_request['head']['sha']}"
112
- puts
113
- puts 'Request title:'
114
- puts " #{@current_request['title']}"
115
- puts
116
- puts 'Merge command:'
117
- puts " git #{exec_cmd}"
118
- puts
119
- puts git_call(exec_cmd)
120
- end
121
-
122
-
123
- # Add an approving comment to the request.
124
- def approve
125
- return unless request_exists?
126
- comment = 'Reviewed and approved.'
127
- response = @github.add_comment source_repo, @current_request['number'], comment
128
- if response[:body] == comment
129
- puts 'Successfully approved request.'
130
- else
131
- puts response[:message]
132
- end
133
- end
134
-
135
-
136
- # Close a specified request.
137
- def close
138
- return unless request_exists?
139
- @github.close_issue source_repo, @current_request['number']
140
- puts 'Successfully closed request.' unless request_exists?('open', @current_request['number'])
141
- end
142
-
143
-
144
- # Prepare local repository to create a new request.
145
- # Sets @local_branch.
146
- def prepare
147
- # Remember original branch the user was currently working on.
148
- @original_branch = source_branch
149
- # People should work on local branches, but especially for single commit changes,
150
- # more often than not, they don't. Therefore we create a branch for them,
151
- # to be able to use code review the way it is intended.
152
- if @original_branch == target_branch
153
- # Unless a branch name is already provided, ask for one.
154
- if (branch_name = @args.shift).nil?
155
- puts 'Please provide a name for the branch:'
156
- branch_name = gets.chomp.gsub(/\W+/, '_').downcase
157
- end
158
- # Create the new branch (as a copy of the current one).
159
- @local_branch = "review_#{Time.now.strftime("%y%m%d")}_#{branch_name}"
160
- git_call "checkout -b #{@local_branch}"
161
- if source_branch == @local_branch
162
- # Stash any uncommitted changes.
163
- git_call('stash') if (save_uncommitted_changes = !git_call('diff HEAD').empty?)
164
- # Go back to master and get rid of pending commits (as these are now on the new branch).
165
- git_call "checkout #{target_branch}"
166
- git_call "reset --hard origin/#{target_branch}"
167
- git_call "checkout #{@local_branch}"
168
- git_call('stash pop') if save_uncommitted_changes
169
- end
170
- else
171
- @local_branch = @original_branch
172
- end
173
- end
174
-
175
-
176
- # Create a new request.
177
- # TODO: Support creating requests to other repositories and branches (like the original repo, this has been forked from).
178
- def create
179
- # Prepare @local_branch.
180
- prepare
181
- # Don't create request with uncommitted changes in current branch.
182
- unless git_call('diff HEAD').empty?
183
- puts 'You have uncommitted changes. Please stash or commit before creating the request.'
184
- return
185
- end
186
- unless git_call("cherry #{target_branch}").empty?
187
- # Push latest commits to the remote branch (and by that, create it if necessary).
188
- git_call "push --set-upstream origin #{@local_branch}", debug_mode, true
189
- # Gather information.
190
- last_request_id = @current_requests.collect { |req| req['number'] }.sort.last.to_i
191
- title, body = create_title_and_body(target_branch)
192
- # Create the actual pull request.
193
- @github.create_pull_request target_repo, target_branch, source_branch, title, body
194
- # Switch back to target_branch and check for success.
195
- git_call "checkout #{target_branch}"
196
- update
197
- potential_new_request = @current_requests.find { |req| req['title'] == title }
198
- if potential_new_request and potential_new_request['number'] > last_request_id
199
- puts "Successfully created new request ##{potential_new_request['number']}"
200
- puts File.join("https://github.com", target_repo, "pull", potential_new_request['number'].to_s)
201
- end
202
- # Return to the user's original branch.
203
- git_call "checkout #{@original_branch}"
204
- else
205
- puts 'Nothing to push to remote yet. Commit something first.'
206
- end
207
- end
208
-
209
-
210
- # Deletes obsolete branches (left over from already closed requests).
211
- def clean
212
- # Pruning is needed to remove already deleted branches from your local track.
213
- git_call 'remote prune origin'
214
- # Determine strategy to clean.
215
- case @args.size
216
- when 0
217
- puts 'Argument missing. Please provide either an ID or the option "--all".'
218
- when 1
219
- if @args.first == '--all'
220
- # git review clean --all
221
- clean_all
222
- else
223
- # git review clean ID
224
- clean_single
225
- end
226
- when 2
227
- # git review clean ID --force
228
- clean_single(@args.last == '--force')
229
- else
230
- puts 'Too many arguments.'
231
- end
232
- end
233
-
234
-
235
- # Start a console session (used for debugging).
236
- def console
237
- puts 'Entering debug console.'
238
- request_exists?
239
- require 'ruby-debug'
240
- Debugger.start
241
- debugger
242
- puts 'Leaving debug console.'
243
- end
244
-
245
-
246
- private
247
-
248
- # Setup variables and call actual commands.
249
- def initialize(args = [])
250
- @args = args
251
- command = args.shift
252
- if command and self.respond_to?(command)
253
- @user, @repo = repo_info
254
- return unless @user && @repo && configure_github_access
255
- update unless command == 'clean'
256
- self.send command
257
- else
258
- unless command.nil? or command.empty? or %w(help -h --help).include?(command)
259
- puts "git-review: '#{command}' is not a valid command.\n\n"
260
- end
261
- help
262
- end
263
- rescue UnprocessableState
264
- puts 'Execution of git-review command stopped.'
265
- end
266
-
267
-
268
- # Show a quick reference of available commands.
269
- def help
270
- puts 'Usage: git review <command>'
271
- puts 'Manage review workflow for projects hosted on GitHub (using pull requests).'
272
- puts
273
- puts 'Available commands:'
274
- puts ' list [--reverse] List all pending requests.'
275
- puts ' show <ID> [--full] Show details for a single request.'
276
- puts ' browse <ID> Open a browser window and review a specified request.'
277
- puts ' checkout <ID> [--branch] Checkout a specified request\'s changes to your local repository.'
278
- puts ' approve <ID> Add an approving comment to a specified request.'
279
- puts ' merge <ID> Accept a specified request by merging it into master.'
280
- puts ' close <ID> Close a specified request.'
281
- puts ' prepare Creates a new local branch for a request.'
282
- puts ' create Create a new request.'
283
- puts ' clean <ID> [--force] Delete a request\'s remote and local branches.'
284
- puts ' clean --all Delete all obsolete branches.'
285
- end
286
-
287
-
288
- # Check existence of specified request and assign @current_request.
289
- def request_exists?(state = 'open', request_id = nil)
290
- # NOTE: If request_id is set explicitly we might need to update to get the
291
- # latest changes from GitHub, as this is called from within another method.
292
- automated = !request_id.nil?
293
- update(state) if automated
294
- request_id ||= @args.shift.to_i
295
- if request_id == 0
296
- puts 'Please specify a valid ID.'
297
- return false
298
- end
299
- @current_request = @current_requests.find { |req| req['number'] == request_id }
300
- unless @current_request
301
- # Additional try to get an older request from Github by specifying the number.
302
- request = @github.pull_request source_repo, request_id
303
- @current_request = request if request.state == state
304
- end
305
- if @current_request
306
- true
307
- else
308
- # No output for automated checks.
309
- puts "Request '#{request_id}' could not be found among all '#{state}' requests." unless automated
310
- false
311
- end
312
- end
313
-
314
-
315
- # Get latest changes from GitHub.
316
- def update(state = 'open')
317
- @current_requests = @github.pull_requests(source_repo, state)
318
- repos = @current_requests.collect do |request|
319
- repo = request.head.repository
320
- "#{repo.owner}/#{repo.name}" if repo
321
- end
322
- repos.uniq.compact.each do |repo|
323
- git_call "fetch git@github.com:#{repo}.git +refs/heads/*:refs/pr/#{repo}/*"
324
- end
325
- end
326
-
327
-
328
- # Cleans a single request's obsolete branches.
329
- def clean_single(force_deletion = false)
330
- update('closed')
331
- return unless request_exists?('closed')
332
- # Ensure there are no unmerged commits or '--force' flag has been set.
333
- branch_name = @current_request['head']['ref']
334
- if unmerged_commits?(branch_name) and not force_deletion
335
- return puts "Won't delete branches that contain unmerged commits. Use '--force' to override."
336
- end
337
- delete_branch(branch_name)
338
- end
339
-
340
-
341
- # Cleans all obsolete branches.
342
- def clean_all
343
- update
344
- # Protect all open requests' branches from deletion.
345
- protected_branches = @current_requests.collect { |request| request['head']['ref'] }
346
- # Select all branches with the correct prefix.
347
- review_branches = all_branches.select { |branch| branch.include?('review_') }
348
- # Only use uniq branch names (no matter if local or remote).
349
- review_branches.collect { |branch| branch.split('/').last }.uniq.each do |branch_name|
350
- # Only clean up obsolete branches.
351
- unless protected_branches.include?(branch_name) or unmerged_commits?(branch_name, false)
352
- delete_branch(branch_name)
353
- end
354
- end
355
- end
356
-
357
-
358
- # Delete local and remote branches that match a given name.
359
- def delete_branch(branch_name)
360
- # Delete local branch if it exists.
361
- git_call("branch -D #{branch_name}", true) if branch_exists?(:local, branch_name)
362
- # Delete remote branch if it exists.
363
- git_call("push origin :#{branch_name}", true) if branch_exists?(:remote, branch_name)
364
- end
365
-
366
-
367
- # Returns a boolean stating whether there are unmerged commits on the local or remote branch.
368
- def unmerged_commits?(branch_name, verbose = true)
369
- locations = []
370
- locations << ['', ''] if branch_exists?(:local, branch_name)
371
- locations << ['origin/', 'origin/'] if branch_exists?(:remote, branch_name)
372
- locations = locations + [['', 'origin/'], ['origin/', '']] if locations.size == 2
373
- if locations.empty?
374
- puts 'Nothing to do. All cleaned up already.' if verbose
375
- return false
376
- end
377
- # Compare remote and local branch with remote and local master.
378
- responses = locations.collect do |location|
379
- git_call "cherry #{location.first}#{target_branch} #{location.last}#{branch_name}"
380
- end
381
- # Select commits (= non empty, not just an error message and not only duplicate commits staring with '-').
382
- unmerged_commits = responses.reject do |response|
383
- response.empty? or response.include?('fatal: Unknown commit') or response.split("\n").reject { |x| x.index('-') == 0 }.empty?
384
- end
385
- # If the array ain't empty, we got unmerged commits.
386
- if unmerged_commits.empty?
387
- false
388
- else
389
- puts "Unmerged commits on branch '#{branch_name}'."
390
- true
391
- end
392
- end
393
-
394
-
395
- # Returns a boolean stating whether a branch exists in a specified location.
396
- def branch_exists?(location, branch_name)
397
- return false unless [:remote, :local].include? location
398
- prefix = location == :remote ? 'remotes/origin/' : ''
399
- all_branches.include?(prefix + branch_name)
400
- end
401
-
402
-
403
- # System call to 'git'.
404
- def git_call(command, verbose = debug_mode, enforce_success = false)
405
- if verbose
406
- puts
407
- puts " git #{command}"
408
- puts
409
- end
410
- output = `git #{command}`
411
- puts output if verbose and not output.empty?
412
- # If we need sth. to succeed, but it doesn't stop right there.
413
- if enforce_success and not last_command_successful?
414
- puts output unless output.empty?
415
- raise UnprocessableState
416
- end
417
- output
418
- end
419
-
420
-
421
- # Show current discussion for @current_request.
422
- def discussion
423
- request = @github.pull_request source_repo, @current_request['number']
424
- # FIXME:
425
- puts 'This needs to be updated to work with API v3.'
426
- return
427
- result = request['discussion'].collect do |entry|
428
- user = entry['user'] || entry['author']
429
- name = user['login'].empty? ? user['name'] : user['login']
430
- output = "\e[35m#{name}\e[m "
431
- case entry['type']
432
- # Comments:
433
- when "IssueComment", "CommitComment", "PullRequestReviewComment"
434
- output << "added a comment"
435
- output << " to \e[36m#{entry['commit_id'][0..6]}\e[m" if entry['commit_id']
436
- output << " on #{format_time(entry['created_at'])}"
437
- unless entry['created_at'] == entry['updated_at']
438
- output << " (updated on #{format_time(entry['updated_at'])})"
439
- end
440
- output << ":\n#{''.rjust(output.length + 1, "-")}\n"
441
- output << "> \e[32m#{entry['path']}:#{entry['position']}\e[m\n" if entry['path'] and entry['position']
442
- output << entry['body']
443
- # Commits:
444
- when "Commit"
445
- output << "authored commit \e[36m#{entry['id'][0..6]}\e[m on #{format_time(entry['authored_date'])}"
446
- unless entry['authored_date'] == entry['committed_date']
447
- output << " (committed on #{format_time(entry['committed_date'])})"
448
- end
449
- output << ":\n#{''.rjust(output.length + 1, "-")}\n#{entry["message"]}"
450
- end
451
- output << "\n\n\n"
452
- end
453
- puts result.compact unless result.empty?
454
- end
455
-
456
-
457
- # Display helper to make output more configurable.
458
- def format_text(info, size)
459
- info.to_s.gsub("\n", ' ')[0, size-1].ljust(size)
460
- end
461
-
462
-
463
- # Display helper to unify time output.
464
- def format_time(time_string)
465
- Time.parse(time_string).strftime('%d-%b-%y')
466
- end
467
-
468
-
469
- # Returns a string that specifies the source repo.
470
- def source_repo
471
- "#{@user}/#{@repo}"
472
- end
473
-
474
-
475
- # Returns a string that specifies the source branch.
476
- def source_branch
477
- git_call('branch').chomp!.match(/\*(.*)/)[0][2..-1]
478
- end
479
-
480
-
481
- # Returns a string consisting of source repo and branch.
482
- def source
483
- "#{source_repo}/#{source_branch}"
484
- end
485
-
486
-
487
- # Returns a string that specifies the target repo.
488
- def target_repo
489
- # TODO: Enable possibility to manually override this and set arbitrary repositories.
490
- source_repo
491
- end
492
-
493
-
494
- # Returns a string that specifies the target branch.
495
- def target_branch
496
- # TODO: Enable possibility to manually override this and set arbitrary branches.
497
- ENV['TARGET_BRANCH'] || 'master'
498
- end
499
-
500
-
501
- # Returns a string consisting of target repo and branch.
502
- def target
503
- "#{target_repo}/#{target_branch}"
504
- end
505
-
506
-
507
- # Returns an Array of all existing branches.
508
- def all_branches
509
- @branches ||= git_call('branch -a').split("\n").collect { |s| s.strip }
510
- end
511
-
512
-
513
- # Returns a boolean stating whether a specified commit has already been merged.
514
- def merged?(sha)
515
- not git_call("rev-list #{sha} ^HEAD 2>&1").split("\n").size > 0
516
- end
517
-
518
-
519
- # Uses Octokit to access GitHub.
520
- def configure_github_access
521
- if Settings.instance.oauth_token
522
- @github = Octokit::Client.new(
523
- :login => Settings.instance.username,
524
- :oauth_token => Settings.instance.oauth_token
525
- )
526
- @github.login
527
- else
528
- configure_oauth
529
- configure_github_access
530
- end
531
- end
532
-
533
-
534
- def debug_mode
535
- Settings.instance.review_mode == 'debug'
536
- end
537
-
538
-
539
- # Collect git config information in a Hash for easy access.
540
- # Checks '~/.gitconfig' for credentials.
541
- def git_config
542
- unless @git_config
543
- # Read @git_config from local git config.
544
- @git_config = { }
545
- config_list = git_call('config --list', false)
546
- config_list.split("\n").each do |line|
547
- key, value = line.split('=')
548
- @git_config[key] = value
549
- end
550
- end
551
- @git_config
552
- end
553
-
554
-
555
- # Returns an array consisting of information on the user and the project.
556
- def repo_info
557
- # Extract user and project name from GitHub URL.
558
- url = git_config['remote.origin.url']
559
- if url.nil?
560
- puts "Error: Not a git repository."
561
- return [nil, nil]
562
- end
563
- user, project = github_user_and_project(url)
564
- # If there are no results yet, look for 'insteadof' substitutions in URL and try again.
565
- unless user && project
566
- short, base = github_insteadof_matching(config_hash, url)
567
- if short and base
568
- url = url.sub(short, base)
569
- user, project = github_user_and_project(url)
570
- end
571
- end
572
- [user, project]
573
- end
574
-
575
-
576
- # Looks for 'insteadof' substitutions in URL.
577
- def github_insteadof_matching(config_hash, url)
578
- first = config_hash.collect { |key, value|
579
- [value, /url\.(.*github\.com.*)\.insteadof/.match(key)]
580
- }.find { |value, match|
581
- url.index(value) and match != nil
582
- }
583
- first ? [first[0], first[1][1]] : [nil, nil]
584
- end
585
-
586
-
587
- # Extract user and project name from GitHub URL.
588
- def github_user_and_project(github_url)
589
- matches = /github\.com.(.*?)\/(.*)/.match(github_url)
590
- matches ? [matches[1], matches[2].sub(/\.git\z/, '')] : [nil, nil]
591
- end
592
-
593
-
594
- # Returns a boolean stating whether the last issued system call was successful.
595
- def last_command_successful?
596
- $?.exitstatus == 0
597
- end
598
-
599
- # Returns an array where the 1st item is the title and the 2nd one is the body
600
- def create_title_and_body(target_branch)
601
- commits = git_call("log --format='%H' HEAD...#{target_branch}").lines.count
602
- puts "commits: #{commits}"
603
- if commits == 1
604
- # we can create a really specific title and body
605
- title = git_call("log --format='%s' HEAD...#{target_branch}").chomp
606
- body = git_call("log --format='%b' HEAD...#{target_branch}").chomp
607
- else
608
- title = "[Review] Request from '#{git_config['github.login']}' @ '#{source}'"
609
- body = "Please review the following changes:\n"
610
- body += git_call("log --oneline HEAD...#{target_branch}").lines.map{|l| " * #{l.chomp}"}.join("\n")
611
- end
612
-
613
- tmpfile = Tempfile.new('git-review')
614
- tmpfile.write(title + "\n\n" + body)
615
- tmpfile.flush
616
- editor = ENV['TERM_EDITOR'] || ENV['EDITOR']
617
- warn "Please set $EDITOR or $TERM_EDITOR in your .bash_profile." unless editor
618
-
619
- system("#{editor || 'open'} #{tmpfile.path}")
620
-
621
- tmpfile.rewind
622
- lines = tmpfile.read.lines.to_a
623
- puts lines.inspect
624
- title = lines.shift.chomp
625
- lines.shift if lines[0].chomp.empty?
626
-
627
- body = lines.join
12
+ # Include all helper functions to make GitReview work as expected.
13
+ require_relative 'git-review/internals'
14
+ # Deal with current git repository.
15
+ require_relative 'git-review/local'
16
+ # Communicate with Github via API.
17
+ require_relative 'git-review/github'
18
+ # Read and write settings from/to the filesystem.
19
+ require_relative 'git-review/settings'
20
+ # Provide available commands.
21
+ require_relative 'git-review/commands'
22
+ # Include all kinds of custom-defined errors.
23
+ require_relative 'git-review/errors'
628
24
 
629
- tmpfile.unlink
630
25
 
631
- [title, body]
632
- end
26
+ module GitReview
633
27
 
634
28
  end
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-review
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.7
5
- prerelease:
4
+ version: 2.0.0.alpha
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Dominik Bamberger
@@ -34,7 +34,7 @@ dependencies:
34
34
  requirements:
35
35
  - - ~>
36
36
  - !ruby/object:Gem::Version
37
- version: 1.24.0
37
+ version: 2.0.0
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +42,7 @@ dependencies:
42
42
  requirements:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
- version: 1.24.0
45
+ version: 2.0.0
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: yajl-ruby
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -60,7 +60,7 @@ dependencies:
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  - !ruby/object:Gem::Dependency
63
- name: rspec
63
+ name: gli
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  none: false
66
66
  requirements:
@@ -75,6 +75,38 @@ dependencies:
75
75
  - - ! '>='
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 2.13.0
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 2.13.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: hashie
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
78
110
  description: Manage review workflow for projects hosted on GitHub (using pull requests).
79
111
  email: bamberger.dominik@gmail.com
80
112
  executables:
@@ -83,7 +115,6 @@ extensions: []
83
115
  extra_rdoc_files: []
84
116
  files:
85
117
  - LICENSE
86
- - lib/settings.rb
87
118
  - lib/git-review.rb
88
119
  - lib/git-review/settings.rb
89
120
  - lib/git-review/commands.rb
@@ -91,7 +122,6 @@ files:
91
122
  - lib/git-review/github.rb
92
123
  - lib/git-review/internals.rb
93
124
  - lib/git-review/local.rb
94
- - lib/oauth_helper.rb
95
125
  - bin/git-review
96
126
  homepage: http://github.com/b4mboo/git-review
97
127
  licenses: []
@@ -108,9 +138,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
108
138
  required_rubygems_version: !ruby/object:Gem::Requirement
109
139
  none: false
110
140
  requirements:
111
- - - ! '>='
141
+ - - ! '>'
112
142
  - !ruby/object:Gem::Version
113
- version: '0'
143
+ version: 1.3.1
114
144
  requirements: []
115
145
  rubyforge_project:
116
146
  rubygems_version: 1.8.23
@@ -1,63 +0,0 @@
1
- require 'net/http'
2
- require 'net/https'
3
- require 'json'
4
- # Required to hide password
5
- require 'io/console'
6
- # Used to retrieve hostname
7
- require 'socket'
8
-
9
- module OAuthHelper
10
- def configure_oauth(chosen_description = nil)
11
- puts "Requesting a OAuth token for git-review."
12
- puts "This procedure will grant access to your public and private repositories."
13
- puts "You can revoke this authorization by visiting the following page: " +
14
- "https://github.com/settings/applications"
15
- print "Plese enter your GitHub's username: "
16
- username = STDIN.gets.chomp
17
- print "Plese enter your GitHub's password (it won't be stored anywhere): "
18
- password = STDIN.noecho(&:gets).chomp
19
- print "\n"
20
-
21
- if chosen_description
22
- description = chosen_description
23
- else
24
- description = "git-review - #{Socket.gethostname}"
25
- puts "Please enter a descriptiont to associate to this token, it will " +
26
- "make easier to find it inside of github's application page."
27
- puts "Press enter to accept the proposed description"
28
- print "Description [#{description}]:"
29
- user_description = STDIN.gets.chomp
30
- description = user_description.empty? ? description : user_description
31
- end
32
-
33
- uri = URI("https://api.github.com/authorizations")
34
-
35
- http = Net::HTTP.new(uri.host, uri.port)
36
- http.use_ssl = true
37
-
38
- req =Net::HTTP::Post.new(uri.request_uri)
39
- req.basic_auth username, password
40
- req.body = {
41
- "scopes" => ["repo"],
42
- "note" => description
43
- }.to_json
44
-
45
- response = http.request req
46
-
47
- if response.code == '401'
48
- warn "You provided the wrong username/password, please try again."
49
- configure_oauth(description)
50
- elsif response.code == '201'
51
- parser_response = JSON.parse(response.body)
52
- settings = Settings.instance
53
- settings.oauth_token = parser_response['token']
54
- settings.username = username
55
- settings.save!
56
- puts "OAuth token successfully created"
57
- else
58
- warn "Something went wrong: #{response.body}"
59
- exit 1
60
- end
61
- end
62
-
63
- end
@@ -1,47 +0,0 @@
1
- require 'fileutils'
2
- require 'singleton'
3
- require 'yaml'
4
-
5
- class Settings
6
- include Singleton
7
-
8
- def initialize
9
- @config_file = File.join(
10
- Dir.home,
11
- '.git_review.yml'
12
- )
13
-
14
- @config = if File.exists?(@config_file)
15
- YAML.load_file(@config_file) || {}
16
- else
17
- {}
18
- end
19
- end
20
-
21
- def save!
22
- File.open(@config_file, 'w') do |file|
23
- file.write(YAML.dump(@config))
24
- end
25
- end
26
-
27
- def review_mode
28
- @config['review_mode']
29
- end
30
-
31
- def oauth_token
32
- @config['oauth_token']
33
- end
34
-
35
- def oauth_token=(token)
36
- @config['oauth_token'] = token
37
- end
38
-
39
- def username
40
- @config['username']
41
- end
42
-
43
- def username=(username)
44
- @config['username'] = username
45
- end
46
-
47
- end