thegarage-gitx 2.2.2 → 2.2.3.pre1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 394005ee7f89f43fbb1864a7e62510f7f3e8f34d
4
- data.tar.gz: debac14bd4e25cc6bc9746e39b85d41d59872888
3
+ metadata.gz: e0b14ee5697a2883977afed2c228ab55bffb1465
4
+ data.tar.gz: aef6ef947211f421c07b0e3aef71da696c2e9c31
5
5
  SHA512:
6
- metadata.gz: 72ce85dbe43da457cdf46d252ffe15fb4540ba8b5fe7d1600ca7e06c947849fbd2c910e972b8c2627dc4663f7d2cba56cf0b1e4522285ce079cbd7856f217c73
7
- data.tar.gz: c67b6292dcabcb5b219e57acb334056e77c69df6a049453ea167ac84e44e206fe0ab47872ec00e9e0a0f8f44ff9cdea7741607ff6359a5ffa6f121ef091b5ba9
6
+ metadata.gz: 0dad8dba2b0b25ef97c4cd0a1628ba88227270ed2ce68de7cc53777206e762c90ce3e6833bd7ea509b0f8ef8f227d33364b8cb38c94fb364bf7e9baad63ada2f
7
+ data.tar.gz: 4ddd4bd7aa598289b85962a2d6065ce40a242090fdb8084382bea447c9e20041e19299d39781453bdf9b5010b51a83fefbbc86ca083fcb7b464b5473940239a4
data/Guardfile CHANGED
@@ -1,7 +1,7 @@
1
1
  # A sample Guardfile
2
2
  # More info at https://github.com/guard/guard#readme
3
3
 
4
- guard :rspec do
4
+ guard :rspec, cmd: 'rspec' do
5
5
  watch(%r{^spec/.+_spec\.rb$})
6
6
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
7
  watch('spec/spec_helper.rb') { "spec" }
@@ -17,8 +17,6 @@ module Thegarage
17
17
  method_option :trace, :type => :boolean, :aliases => '-v'
18
18
  def initialize(*args)
19
19
  super(*args)
20
- RestClient.proxy = ENV['HTTPS_PROXY'] if ENV.has_key?('HTTPS_PROXY')
21
- RestClient.log = Logger.new(STDOUT) if options[:trace]
22
20
  end
23
21
 
24
22
  private
@@ -21,7 +21,7 @@ module Thegarage
21
21
  say target_branch, :green
22
22
 
23
23
  refresh_branch_from_remote target_branch
24
- run_cmd "git pull . #{branch}"
24
+ run_cmd "git merge #{branch}"
25
25
  run_cmd "git push origin HEAD"
26
26
  checkout_branch branch
27
27
  end
@@ -2,8 +2,6 @@ require 'thor'
2
2
  require 'thegarage/gitx'
3
3
  require 'thegarage/gitx/cli/base_command'
4
4
  require 'thegarage/gitx/cli/update_command'
5
- require 'json'
6
- require 'rest_client'
7
5
  require 'octokit'
8
6
 
9
7
  module Thegarage
@@ -29,37 +27,48 @@ module Thegarage
29
27
  fail 'Github authorization token not found' unless authorization_token
30
28
 
31
29
  branch = current_branch.name
32
- pull_request = find_pull_request(branch)
33
- if pull_request.nil?
34
- UpdateCommand.new.update
35
- changelog = run_cmd "git log #{Thegarage::Gitx::BASE_BRANCH}...#{branch} --no-merges --pretty=format:'* %s%n%b'"
36
- pull_request = create_pull_request(branch, changelog, options)
37
- say 'Pull request created: '
38
- say pull_request['html_url'], :green
39
- end
40
- assign_pull_request(pull_request, options[:assignee]) if options[:assignee]
30
+ pull_request = find_or_create_pull_request(branch)
31
+ assign_pull_request(pull_request) if options[:assignee]
41
32
 
42
- run_cmd "open #{pull_request['html_url']}" if options[:open]
33
+ run_cmd "open #{pull_request.html_url}" if options[:open]
43
34
  end
44
35
 
45
36
  private
46
37
 
47
- # returns [Hash] data structure of created pull request
48
- # request github authorization token
49
- # User-Agent is required
50
- # store the token in local git config
51
- # @returns [String] auth token stored in git (current repo, user config or installed global settings)
38
+ def find_or_create_pull_request(branch)
39
+ pull_request = find_pull_request(branch)
40
+ return pull_request if pull_request
41
+
42
+ UpdateCommand.new.update
43
+ pull_request = create_pull_request(branch)
44
+ say 'Pull request created: '
45
+ say pull_request.html_url, :green
46
+
47
+ pull_request
48
+ end
49
+
50
+ # token is cached in local git config for future use
51
+ # @return [String] auth token stored in git (current repo, user config or installed global settings)
52
52
  # @see http://developer.github.com/v3/oauth/#scopes
53
53
  # @see http://developer.github.com/v3/#user-agent-required
54
54
  def authorization_token
55
55
  auth_token = repo.config['thegarage.gitx.githubauthtoken']
56
56
  return auth_token unless auth_token.to_s.blank?
57
57
 
58
- fail "Github user not configured. Run: `git config --global github.user 'me@email.com'`" unless username
58
+ auth_token = create_authorization
59
+ repo.config['thegarage.gitx.githubauthtoken'] = auth_token
60
+ auth_token
61
+ end
62
+
63
+ def create_authorization
59
64
  password = ask("Github password for #{username}: ", :echo => false)
60
65
  say ''
61
- two_factor_auth_token = ask("Github two factor authorization token (if enabled): ", :echo => false)
66
+ client = Octokit::Client.new(login: username, password: password)
67
+ response = client.create_authorization(authorization_request_options)
68
+ response.token
69
+ end
62
70
 
71
+ def authorization_request_options
63
72
  timestamp = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S%z')
64
73
  client_name = "The Garage Git eXtensions - #{remote_origin_name} #{timestamp}"
65
74
  options = {
@@ -67,18 +76,14 @@ module Thegarage
67
76
  :note => client_name,
68
77
  :note_url => CLIENT_URL
69
78
  }
79
+ two_factor_auth_token = ask("Github two factor authorization token (if enabled): ", :echo => false)
80
+ say ''
70
81
  options[:headers] = {'X-GitHub-OTP' => two_factor_auth_token} if two_factor_auth_token
71
- client = Octokit::Client.new(login: username, password: password)
72
- response = client.create_authorization(options)
73
- token = response.token
74
- repo.config['thegarage.gitx.githubauthtoken'] = token
75
- token
82
+ options
76
83
  end
77
84
 
78
85
  # @see http://developer.github.com/v3/pulls/
79
- def create_pull_request(branch, changelog, options = {})
80
- body = pull_request_body(changelog, options[:description])
81
-
86
+ def create_pull_request(branch)
82
87
  say "Creating pull request for "
83
88
  say "#{branch} ", :green
84
89
  say "against "
@@ -86,71 +91,45 @@ module Thegarage
86
91
  say "in "
87
92
  say remote_origin_name, :green
88
93
 
89
- payload = {
90
- :title => branch,
91
- :base => Thegarage::Gitx::BASE_BRANCH,
92
- :head => branch,
93
- :body => body
94
- }.to_json
95
- response = RestClient::Request.new(:url => pull_request_url, :method => "POST", :payload => payload, :headers => request_headers).execute
96
- pull_request = JSON.parse response.body
97
-
98
- pull_request
99
- rescue RestClient::Exception => e
100
- process_error e
94
+ client = Octokit::Client.new(:access_token => authorization_token)
95
+ title = branch
96
+ body = pull_request_body(branch)
97
+ client.create_pull_request(remote_origin_name, Thegarage::Gitx::BASE_BRANCH, branch, title, body)
101
98
  end
102
99
 
103
- def assign_pull_request(pull_request, assignee)
100
+ def assign_pull_request(pull_request)
101
+ assignee = options[:assignee]
104
102
  say "Assigning pull request to "
105
103
  say assignee, :green
106
104
 
107
- branch = pull_request['head']['ref']
108
- payload = {
109
- :title => branch,
110
- :assignee => assignee
111
- }.to_json
112
- RestClient::Request.new(:url => pull_request['issue_url'], :method => "PATCH", :payload => payload, :headers => request_headers).execute
113
- rescue RestClient::Exception => e
114
- process_error e
105
+ client = Octokit::Client.new(:access_token => authorization_token)
106
+ title = pull_request.title
107
+ body = pull_request.body
108
+ options = {
109
+ assignee: assignee
110
+ }
111
+ client.update_issue(remote_origin_name, pull_request.number, title, body, options)
115
112
  end
116
113
 
117
- # @returns [Hash] data structure of pull request info if found
118
- # @returns nil if no pull request found
114
+ # @return [Sawyer::Resource] data structure of pull request info if found
115
+ # @return nil if no pull request found
119
116
  def find_pull_request(branch)
120
- head_reference = [remote_origin_name.split('/').first, branch].join(':')
117
+ head_reference = "#{repo_organization_name}:#{branch}"
121
118
  params = {
122
119
  head: head_reference,
123
120
  state: 'open'
124
121
  }
125
- response = RestClient::Request.new(:url => pull_request_url, :method => "GET", :headers => request_headers.merge(params: params)).execute
126
- data = JSON.parse(response.body)
127
- data.first
128
- rescue RestClient::Exception => e
129
- process_error e
130
- end
131
-
132
- def process_error(e)
133
- data = JSON.parse e.http_body
134
- say "Github request failed: #{data['message']}", :red
135
- throw e
136
- end
137
-
138
- def pull_request_url
139
- "https://api.github.com/repos/#{remote_origin_name}/pulls"
140
- end
141
-
142
- def request_headers
143
- {
144
- :accept => :json,
145
- :content_type => :json,
146
- 'Authorization' => "token #{authorization_token}"
147
- }
122
+ client = Octokit::Client.new(:access_token => authorization_token)
123
+ pull_requests = client.pull_requests(remote_origin_name, params)
124
+ pull_requests.first
148
125
  end
149
126
 
150
- # @returns [String] github username (ex: 'wireframe') of the current github.user
151
- # @returns empty [String] when no github.user is set on the system
127
+ # @return [String] github username (ex: 'wireframe') of the current github.user
128
+ # @raise error if github.user is not configured
152
129
  def username
153
- repo.config['github.user']
130
+ username = repo.config['github.user']
131
+ fail "Github user not configured. Run: `git config --global github.user 'me@email.com'`" unless username
132
+ username
154
133
  end
155
134
 
156
135
  # lookup the current repository of the PWD
@@ -160,7 +139,14 @@ module Thegarage
160
139
  remote.to_s.gsub(/\.git$/,'').split(/[:\/]/).last(2).join('/')
161
140
  end
162
141
 
163
- def pull_request_body(changelog, description = nil)
142
+ def repo_organization_name
143
+ remote_origin_name.split('/').first
144
+ end
145
+
146
+ def pull_request_body(branch)
147
+ changelog = run_cmd "git log #{Thegarage::Gitx::BASE_BRANCH}...#{branch} --no-merges --pretty=format:'* %s%n%b'"
148
+ description = options[:description]
149
+
164
150
  description_template = []
165
151
  description_template << "#{description}\n" if description
166
152
  description_template << '### Changelog'
@@ -1,5 +1,5 @@
1
1
  module Thegarage
2
2
  module Gitx
3
- VERSION = '2.2.2'
3
+ VERSION = '2.2.3.pre1'
4
4
  end
5
5
  end
@@ -0,0 +1,81 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://api.github.com/repos/thegarage/thegarage-gitx/pulls?head=thegarage:feature-branch&state=open
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - application/vnd.github.v3+json
12
+ User-Agent:
13
+ - Octokit Ruby Gem 3.2.0
14
+ Authorization:
15
+ - token 123123
16
+ Accept-Encoding:
17
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Server:
24
+ - GitHub.com
25
+ Date:
26
+ - Tue, 05 Aug 2014 16:36:03 GMT
27
+ Content-Type:
28
+ - application/json; charset=utf-8
29
+ Status:
30
+ - 200 OK
31
+ X-Ratelimit-Limit:
32
+ - '5000'
33
+ X-Ratelimit-Remaining:
34
+ - '4991'
35
+ X-Ratelimit-Reset:
36
+ - '1407257585'
37
+ Cache-Control:
38
+ - private, max-age=60, s-maxage=60
39
+ Etag:
40
+ - '"6d00d48abf2adf1877c8244700cd4c6f"'
41
+ X-Oauth-Scopes:
42
+ - repo
43
+ X-Accepted-Oauth-Scopes:
44
+ - ''
45
+ Vary:
46
+ - Accept, Authorization, Cookie, X-GitHub-OTP
47
+ - Accept-Encoding
48
+ X-Github-Media-Type:
49
+ - github.v3; format=json
50
+ Link:
51
+ - <https://api.github.com/repositories/17608725/pulls?head=thegarage%3Afeature-branch&state=open&page=0>;
52
+ rel="last"
53
+ X-Xss-Protection:
54
+ - 1; mode=block
55
+ X-Frame-Options:
56
+ - deny
57
+ Content-Security-Policy:
58
+ - default-src 'none'
59
+ Content-Length:
60
+ - '2'
61
+ Access-Control-Allow-Credentials:
62
+ - 'true'
63
+ Access-Control-Expose-Headers:
64
+ - ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset,
65
+ X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
66
+ Access-Control-Allow-Origin:
67
+ - "*"
68
+ X-Github-Request-Id:
69
+ - 46C5E25C:1E4F:1642AD9:53E107F3
70
+ Strict-Transport-Security:
71
+ - max-age=31536000; includeSubdomains
72
+ X-Content-Type-Options:
73
+ - nosniff
74
+ X-Served-By:
75
+ - d818ddef80f4c7d10683dd483558952a
76
+ body:
77
+ encoding: UTF-8
78
+ string: '[{"html_url":"https://path/to/html/pull/request","issue_url":"https://api/path/to/issue/url","number":10,"head":{"ref":"branch_name"}}]'
79
+ http_version:
80
+ recorded_at: Tue, 05 Aug 2014 16:36:03 GMT
81
+ recorded_with: VCR 2.9.2
@@ -0,0 +1,65 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://api.github.com/repos/thegarage/thegarage-gitx/pulls?head=thegarage:feature-branch&state=open
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - application/vnd.github.v3+json
12
+ User-Agent:
13
+ - Octokit Ruby Gem 3.2.0
14
+ Authorization:
15
+ - token 123123
16
+ Accept-Encoding:
17
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Server:
24
+ - GitHub.com
25
+ Date:
26
+ - Tue, 05 Aug 2014 17:14:02 GMT
27
+ Content-Type:
28
+ - application/json; charset=utf-8
29
+ Status:
30
+ - 401 Unauthorized
31
+ X-Github-Media-Type:
32
+ - github.v3; format=json
33
+ X-Ratelimit-Limit:
34
+ - '60'
35
+ X-Ratelimit-Remaining:
36
+ - '59'
37
+ X-Ratelimit-Reset:
38
+ - '1407262442'
39
+ X-Xss-Protection:
40
+ - 1; mode=block
41
+ X-Frame-Options:
42
+ - deny
43
+ Content-Security-Policy:
44
+ - default-src 'none'
45
+ Content-Length:
46
+ - '83'
47
+ Access-Control-Allow-Credentials:
48
+ - 'true'
49
+ Access-Control-Expose-Headers:
50
+ - ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset,
51
+ X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
52
+ Access-Control-Allow-Origin:
53
+ - "*"
54
+ X-Github-Request-Id:
55
+ - 46C5E25C:552F:1BC5411:53E110DA
56
+ Strict-Transport-Security:
57
+ - max-age=31536000; includeSubdomains
58
+ X-Content-Type-Options:
59
+ - nosniff
60
+ body:
61
+ encoding: UTF-8
62
+ string: '[]'
63
+ http_version:
64
+ recorded_at: Tue, 05 Aug 2014 17:14:02 GMT
65
+ recorded_with: VCR 2.9.2
@@ -0,0 +1,7 @@
1
+ # protip to prevent execution of shell commands during testsuite
2
+ # see http://stackoverflow.com/questions/1628586/mock-system-call-in-ruby
3
+ module Kernel
4
+ def `(cmd)
5
+ puts "stubbing execution within tests of command: #{cmd}"
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ require 'vcr'
2
+
3
+ VCR.configure do |c|
4
+ c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
5
+ c.hook_into :webmock
6
+ end
@@ -28,7 +28,7 @@ describe Thegarage::Gitx::Cli::IntegrateCommand do
28
28
  expect(cli).to receive(:run_cmd).with("git branch -D staging", allow_failure: true).ordered
29
29
  expect(cli).to receive(:run_cmd).with("git fetch origin").ordered
30
30
  expect(cli).to receive(:run_cmd).with("git checkout staging").ordered
31
- expect(cli).to receive(:run_cmd).with("git pull . feature-branch").ordered
31
+ expect(cli).to receive(:run_cmd).with("git merge feature-branch").ordered
32
32
  expect(cli).to receive(:run_cmd).with("git push origin HEAD").ordered
33
33
  expect(cli).to receive(:run_cmd).with("git checkout feature-branch").ordered
34
34
 
@@ -45,7 +45,7 @@ describe Thegarage::Gitx::Cli::IntegrateCommand do
45
45
  expect(cli).to receive(:run_cmd).with("git branch -D prototype", allow_failure: true).ordered
46
46
  expect(cli).to receive(:run_cmd).with("git fetch origin").ordered
47
47
  expect(cli).to receive(:run_cmd).with("git checkout prototype").ordered
48
- expect(cli).to receive(:run_cmd).with("git pull . feature-branch").ordered
48
+ expect(cli).to receive(:run_cmd).with("git merge feature-branch").ordered
49
49
  expect(cli).to receive(:run_cmd).with("git push origin HEAD").ordered
50
50
  expect(cli).to receive(:run_cmd).with("git checkout feature-branch").ordered
51
51
 
@@ -21,30 +21,34 @@ describe Thegarage::Gitx::Cli::ReviewCommand do
21
21
  before do
22
22
  allow(cli).to receive(:repo).and_return(repo)
23
23
  allow(cli).to receive(:current_branch).and_return(branch)
24
+ allow(cli).to receive(:input_from_editor).and_return('description')
24
25
  end
25
26
 
26
27
  describe '#review' do
27
- let(:pull_request) do
28
- {
29
- 'html_url' => 'https://path/to/new/pull/request',
30
- 'issue_url' => 'https://api/path/to/new/pull/request',
31
- 'head' => {
32
- 'ref' => 'branch_name'
33
- }
34
- }
35
- end
36
28
  context 'when pull request does not exist' do
37
29
  let(:authorization_token) { '123123' }
38
30
  let(:changelog) { '* made some fixes' }
39
31
  let(:fake_update_command) { double('fake update command', update: nil) }
32
+ let(:new_pull_request) do
33
+ {
34
+ html_url: "https://path/to/html/pull/request",
35
+ issue_url: "https://api/path/to/issue/url",
36
+ number: 10,
37
+ head: {
38
+ ref: "branch_name"
39
+ }
40
+ }
41
+ end
40
42
  before do
41
43
  expect(Thegarage::Gitx::Cli::UpdateCommand).to receive(:new).and_return(fake_update_command)
42
44
 
43
- expect(cli).to receive(:authorization_token).and_return(authorization_token)
44
- expect(cli).to receive(:find_pull_request).and_return(nil)
45
- expect(cli).to receive(:create_pull_request).and_return(pull_request)
45
+ allow(cli).to receive(:authorization_token).and_return(authorization_token)
46
46
  expect(cli).to receive(:run_cmd).with("git log master...feature-branch --no-merges --pretty=format:'* %s%n%b'").and_return("2013-01-01 did some stuff").ordered
47
- cli.review
47
+
48
+ stub_request(:post, 'https://api.github.com/repos/thegarage/thegarage-gitx/pulls').to_return(:status => 201, :body => new_pull_request.to_json, :headers => {'Content-Type' => 'application/json'})
49
+ VCR.use_cassette('pull_request_does_not_exist') do
50
+ cli.review
51
+ end
48
52
  end
49
53
  it 'creates github pull request' do
50
54
  should meet_expectations
@@ -56,18 +60,19 @@ describe Thegarage::Gitx::Cli::ReviewCommand do
56
60
  context 'when authorization_token is missing' do
57
61
  let(:authorization_token) { nil }
58
62
  it do
59
- expect(cli).to receive(:authorization_token).and_return(authorization_token)
63
+ allow(cli).to receive(:authorization_token).and_return(authorization_token)
60
64
  expect { cli.review }.to raise_error(/token not found/)
61
65
  end
62
66
  end
63
67
  context 'when pull request already exists' do
64
68
  let(:authorization_token) { '123123' }
65
69
  before do
66
- expect(cli).to receive(:authorization_token).and_return(authorization_token)
67
- expect(cli).to receive(:find_pull_request).and_return(pull_request)
70
+ allow(cli).to receive(:authorization_token).and_return(authorization_token)
68
71
  expect(cli).to_not receive(:create_pull_request)
69
72
 
70
- cli.review
73
+ VCR.use_cassette('pull_request_does_exist') do
74
+ cli.review
75
+ end
71
76
  end
72
77
  it 'does not create new pull request' do
73
78
  should meet_expectations
@@ -81,17 +86,16 @@ describe Thegarage::Gitx::Cli::ReviewCommand do
81
86
  end
82
87
  let(:authorization_token) { '123123' }
83
88
  before do
84
- expect(cli).to receive(:authorization_token).and_return(authorization_token).at_least(:once)
85
- expect(cli).to receive(:find_pull_request).and_return(pull_request)
89
+ allow(cli).to receive(:authorization_token).and_return(authorization_token)
86
90
 
87
- stub_request(:patch, 'https://api/path/to/new/pull/request').to_return(:status => 200)
91
+ stub_request(:patch, 'https://api.github.com/repos/thegarage/thegarage-gitx/issues/10').to_return(:status => 200)
88
92
 
89
- cli.review
93
+ VCR.use_cassette('pull_request_does_exist') do
94
+ cli.review
95
+ end
90
96
  end
91
97
  it 'updates github pull request' do
92
- expect(WebMock).to have_requested(:patch, "https://api/path/to/new/pull/request").
93
- with(:body => {title: 'branch_name', assignee: 'johndoe'}.to_json,
94
- :headers => {'Accept'=>'application/json', 'Authorization'=>'token 123123', 'Content-Type'=>'application/json'})
98
+ expect(WebMock).to have_requested(:patch, "https://api.github.com/repos/thegarage/thegarage-gitx/issues/10")
95
99
  end
96
100
  end
97
101
  context 'when --open flag passed' do
@@ -102,10 +106,11 @@ describe Thegarage::Gitx::Cli::ReviewCommand do
102
106
  end
103
107
  let(:authorization_token) { '123123' }
104
108
  before do
105
- expect(cli).to receive(:authorization_token).and_return(authorization_token)
106
- expect(cli).to receive(:find_pull_request).and_return(pull_request)
107
- expect(cli).to receive(:run_cmd).with("open #{pull_request['html_url']}").ordered
108
- cli.review
109
+ allow(cli).to receive(:authorization_token).and_return(authorization_token)
110
+ expect(cli).to receive(:run_cmd).with("open https://path/to/html/pull/request").ordered
111
+ VCR.use_cassette('pull_request_does_exist') do
112
+ cli.review
113
+ end
109
114
  end
110
115
  it 'runs open command with pull request url' do
111
116
  should meet_expectations
@@ -184,29 +189,4 @@ describe Thegarage::Gitx::Cli::ReviewCommand do
184
189
  it { expect(@auth_token).to eq authorization_token }
185
190
  end
186
191
  end
187
- describe '#create_pull_request' do
188
- context 'when there is an existing authorization_token' do
189
- let(:authorization_token) { '123981239123' }
190
- let(:repo_config) do
191
- {
192
- 'remote.origin.url' => 'https://github.com/thegarage/thegarage-gitx',
193
- 'github.user' => 'ryan@codecrate.com',
194
- 'thegarage.gitx.githubauthtoken' => authorization_token
195
- }
196
- end
197
- before do
198
- stub_request(:post, "https://api.github.com/repos/thegarage/thegarage-gitx/pulls").
199
- to_return(:status => 200, :body => %q({"html_url": "http://github.com/repo/project/pulls/1"}), :headers => {})
200
-
201
- expect(cli).to receive(:input_from_editor).and_return('scrubbed text')
202
- cli.send(:create_pull_request, 'example-branch', 'changelog')
203
- end
204
- it 'should create github pull request' do
205
- should meet_expectations
206
- end
207
- it 'should run expected commands' do
208
- should meet_expectations
209
- end
210
- end
211
- end
212
192
  end
@@ -20,7 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_runtime_dependency "rugged", '~> 0.21.0'
22
22
  spec.add_runtime_dependency "thor"
23
- spec.add_runtime_dependency "rest-client", ">= 1.4.0"
24
23
  spec.add_runtime_dependency "octokit"
25
24
 
26
25
  spec.add_development_dependency "bundler", "~> 1.3"
@@ -29,6 +28,7 @@ Gem::Specification.new do |spec|
29
28
  spec.add_development_dependency "pry", '>= 0'
30
29
  spec.add_development_dependency "webmock", '>= 0'
31
30
  spec.add_development_dependency "timecop", "~> 0.7.0"
31
+ spec.add_development_dependency "vcr"
32
32
  spec.add_development_dependency "guard"
33
33
  spec.add_development_dependency "guard-rspec"
34
34
  spec.add_development_dependency "coveralls"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thegarage-gitx
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.2
4
+ version: 2.2.3.pre1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Sonnek
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-12 00:00:00.000000000 Z
11
+ date: 2014-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rugged
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rest-client
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: 1.4.0
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: 1.4.0
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: octokit
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +136,20 @@ dependencies:
150
136
  - - "~>"
151
137
  - !ruby/object:Gem::Version
152
138
  version: 0.7.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: vcr
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: guard
155
155
  requirement: !ruby/object:Gem::Requirement
@@ -207,7 +207,6 @@ executables:
207
207
  - git-start
208
208
  - git-track
209
209
  - git-update
210
- - git-wtf
211
210
  extensions: []
212
211
  extra_rdoc_files: []
213
212
  files:
@@ -232,7 +231,6 @@ files:
232
231
  - bin/git-start
233
232
  - bin/git-track
234
233
  - bin/git-update
235
- - bin/git-wtf
236
234
  - lib/thegarage/gitx.rb
237
235
  - lib/thegarage/gitx/cli/base_command.rb
238
236
  - lib/thegarage/gitx/cli/buildtag_command.rb
@@ -245,13 +243,16 @@ files:
245
243
  - lib/thegarage/gitx/cli/start_command.rb
246
244
  - lib/thegarage/gitx/cli/track_command.rb
247
245
  - lib/thegarage/gitx/cli/update_command.rb
248
- - lib/thegarage/gitx/runner.rb
249
246
  - lib/thegarage/gitx/string_extensions.rb
250
247
  - lib/thegarage/gitx/version.rb
248
+ - spec/fixtures/vcr_cassettes/pull_request_does_exist.yml
249
+ - spec/fixtures/vcr_cassettes/pull_request_does_not_exist.yml
251
250
  - spec/spec_helper.rb
252
251
  - spec/support/matchers/meet_expectations_matcher.rb
253
252
  - spec/support/pry.rb
253
+ - spec/support/stub_execution.rb
254
254
  - spec/support/timecop.rb
255
+ - spec/support/vcr.rb
255
256
  - spec/support/webmock.rb
256
257
  - spec/thegarage/gitx/cli/buildtag_command_spec.rb
257
258
  - spec/thegarage/gitx/cli/cleanup_command_spec.rb
@@ -279,9 +280,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
279
280
  version: '0'
280
281
  required_rubygems_version: !ruby/object:Gem::Requirement
281
282
  requirements:
282
- - - ">="
283
+ - - ">"
283
284
  - !ruby/object:Gem::Version
284
- version: '0'
285
+ version: 1.3.1
285
286
  requirements: []
286
287
  rubyforge_project:
287
288
  rubygems_version: 2.2.2
@@ -289,10 +290,14 @@ signing_key:
289
290
  specification_version: 4
290
291
  summary: Utility scripts for Git to increase productivity for common operations
291
292
  test_files:
293
+ - spec/fixtures/vcr_cassettes/pull_request_does_exist.yml
294
+ - spec/fixtures/vcr_cassettes/pull_request_does_not_exist.yml
292
295
  - spec/spec_helper.rb
293
296
  - spec/support/matchers/meet_expectations_matcher.rb
294
297
  - spec/support/pry.rb
298
+ - spec/support/stub_execution.rb
295
299
  - spec/support/timecop.rb
300
+ - spec/support/vcr.rb
296
301
  - spec/support/webmock.rb
297
302
  - spec/thegarage/gitx/cli/buildtag_command_spec.rb
298
303
  - spec/thegarage/gitx/cli/cleanup_command_spec.rb
data/bin/git-wtf DELETED
@@ -1,364 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- HELP = <<EOS
4
- git-wtf displays the state of your repository in a readable, easy-to-scan
5
- format. It's useful for getting a summary of how a branch relates to a remote
6
- server, and for wrangling many topic branches.
7
-
8
- git-wtf can show you:
9
- - How a branch relates to the remote repo, if it's a tracking branch.
10
- - How a branch relates to integration branches, if it's a feature branch.
11
- - How a branch relates to the feature branches, if it's an integration
12
- branch.
13
-
14
- git-wtf is best used before a git push, or between a git fetch and a git
15
- merge. Be sure to set color.ui to auto or yes for maximum viewing pleasure.
16
- EOS
17
-
18
- KEY = <<EOS
19
- KEY:
20
- () branch only exists locally
21
- {} branch only exists on a remote repo
22
- [] branch exists locally and remotely
23
-
24
- x merge occurs both locally and remotely
25
- ~ merge occurs only locally
26
- (space) branch isn't merged in
27
-
28
- (It's possible for merges to occur remotely and not locally, of course, but
29
- that's a less common case and git-wtf currently doesn't display anything
30
- special for it.)
31
- EOS
32
-
33
- USAGE = <<EOS
34
- Usage: git wtf [branch+] [options]
35
-
36
- If [branch] is not specified, git-wtf will use the current branch. The possible
37
- [options] are:
38
-
39
- -l, --long include author info and date for each commit
40
- -a, --all show all branches across all remote repos, not just
41
- those from origin
42
- -A, --all-commits show all commits, not just the first 5
43
- -s, --short don't show commits
44
- -k, --key show key
45
- -r, --relations show relation to features / integration branches
46
- --dump-config print out current configuration and exit
47
-
48
- git-wtf uses some heuristics to determine which branches are integration
49
- branches, and which are feature branches. (Specifically, it assumes the
50
- integration branches are named "master", "next" and "edge".) If it guesses
51
- incorrectly, you will have to create a .git-wtfrc file.
52
-
53
- To start building a configuration file, run "git-wtf --dump-config >
54
- .git-wtfrc" and edit it. The config file is a YAML file that specifies the
55
- integration branches, any branches to ignore, and the max number of commits to
56
- display when --all-commits isn't used. git-wtf will look for a .git-wtfrc file
57
- starting in the current directory, and recursively up to the root.
58
-
59
- IMPORTANT NOTE: all local branches referenced in .git-wtfrc must be prefixed
60
- with heads/, e.g. "heads/master". Remote branches must be of the form
61
- remotes/<remote>/<branch>.
62
- EOS
63
-
64
- COPYRIGHT = <<EOS
65
- git-wtf Copyright 2008--2009 William Morgan <wmorgan at the masanjin dot nets>.
66
- This program is free software: you can redistribute it and/or modify it
67
- under the terms of the GNU General Public License as published by the Free
68
- Software Foundation, either version 3 of the License, or (at your option)
69
- any later version.
70
-
71
- This program is distributed in the hope that it will be useful, but WITHOUT
72
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
73
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
74
- more details.
75
-
76
- You can find the GNU General Public License at: http://www.gnu.org/licenses/
77
- EOS
78
-
79
- require 'yaml'
80
- CONFIG_FN = ".git-wtfrc"
81
-
82
- class Numeric; def pluralize s; "#{to_s} #{s}" + (self != 1 ? "s" : "") end end
83
-
84
- if ARGV.delete("--help") || ARGV.delete("-h")
85
- puts USAGE
86
- exit
87
- end
88
-
89
- ## poor man's trollop
90
- $long = ARGV.delete("--long") || ARGV.delete("-l")
91
- $short = ARGV.delete("--short") || ARGV.delete("-s")
92
- $all = ARGV.delete("--all") || ARGV.delete("-a")
93
- $all_commits = ARGV.delete("--all-commits") || ARGV.delete("-A")
94
- $dump_config = ARGV.delete("--dump-config")
95
- $key = ARGV.delete("--key") || ARGV.delete("-k")
96
- $show_relations = ARGV.delete("--relations") || ARGV.delete("-r")
97
- ARGV.each { |a| abort "Error: unknown argument #{a}." if a =~ /^--/ }
98
-
99
- ## search up the path for a file
100
- def find_file fn
101
- while true
102
- return fn if File.exist? fn
103
- fn2 = File.join("..", fn)
104
- return nil if File.expand_path(fn2) == File.expand_path(fn)
105
- fn = fn2
106
- end
107
- end
108
-
109
- want_color = `git config color.wtf`
110
- want_color = `git config color.ui` if want_color.empty?
111
- $color = case want_color.chomp
112
- when "true"; true
113
- when "auto"; $stdout.tty?
114
- end
115
-
116
- def red s; $color ? "\033[31m#{s}\033[0m" : s end
117
- def green s; $color ? "\033[32m#{s}\033[0m" : s end
118
- def yellow s; $color ? "\033[33m#{s}\033[0m" : s end
119
- def cyan s; $color ? "\033[36m#{s}\033[0m" : s end
120
- def grey s; $color ? "\033[1;30m#{s}\033[0m" : s end
121
- def purple s; $color ? "\033[35m#{s}\033[0m" : s end
122
-
123
- ## the set of commits in 'to' that aren't in 'from'.
124
- ## if empty, 'to' has been merged into 'from'.
125
- def commits_between from, to
126
- if $long
127
- `git log --pretty=format:"- %s [#{yellow "%h"}] (#{purple "%ae"}; %ar)" #{from}..#{to}`
128
- else
129
- `git log --pretty=format:"- %s [#{yellow "%h"}]" #{from}..#{to}`
130
- end.split(/[\r\n]+/)
131
- end
132
-
133
- def show_commits commits, prefix=" "
134
- if commits.empty?
135
- puts "#{prefix} none"
136
- else
137
- max = $all_commits ? commits.size : $config["max_commits"]
138
- max -= 1 if max == commits.size - 1 # never show "and 1 more"
139
- commits[0 ... max].each { |c| puts "#{prefix}#{c}" }
140
- puts grey("#{prefix}... and #{commits.size - max} more (use -A to see all).") if commits.size > max
141
- end
142
- end
143
-
144
- def ahead_behind_string ahead, behind
145
- [ahead.empty? ? nil : "#{ahead.size.pluralize 'commit'} ahead",
146
- behind.empty? ? nil : "#{behind.size.pluralize 'commit'} behind"].
147
- compact.join("; ")
148
- end
149
-
150
- def widget merged_in, remote_only=false, local_only=false, local_only_merge=false
151
- left, right = case
152
- when remote_only; %w({ })
153
- when local_only; %w{( )}
154
- else %w([ ])
155
- end
156
- middle = case
157
- when merged_in && local_only_merge; green("~")
158
- when merged_in; green("x")
159
- else " "
160
- end
161
- print left, middle, right
162
- end
163
-
164
- def show b
165
- have_both = b[:local_branch] && b[:remote_branch]
166
-
167
- pushc, pullc, oosync = if have_both
168
- [x = commits_between(b[:remote_branch], b[:local_branch]),
169
- y = commits_between(b[:local_branch], b[:remote_branch]),
170
- !x.empty? && !y.empty?]
171
- end
172
-
173
- if b[:local_branch]
174
- puts "Local branch: " + green(b[:local_branch].sub(/^heads\//, ""))
175
-
176
- if have_both
177
- if pushc.empty?
178
- puts "#{widget true} in sync with remote"
179
- else
180
- action = oosync ? "push after rebase / merge" : "push"
181
- puts "#{widget false} NOT in sync with remote (you should #{action})"
182
- show_commits pushc unless $short
183
- end
184
- end
185
- end
186
-
187
- if b[:remote_branch]
188
- puts "Remote branch: #{cyan b[:remote_branch]} (#{b[:remote_url]})"
189
-
190
- if have_both
191
- if pullc.empty?
192
- puts "#{widget true} in sync with local"
193
- else
194
- action = pushc.empty? ? "merge" : "rebase / merge"
195
- puts "#{widget false} NOT in sync with local (you should #{action})"
196
- show_commits pullc unless $short
197
- end
198
- end
199
- end
200
-
201
- puts "\n#{red "WARNING"}: local and remote branches have diverged. A merge will occur unless you rebase." if oosync
202
- end
203
-
204
- def show_relations b, all_branches
205
- ibs, fbs = all_branches.partition { |name, br| $config["integration-branches"].include?(br[:local_branch]) || $config["integration-branches"].include?(br[:remote_branch]) }
206
- if $config["integration-branches"].include? b[:local_branch]
207
- puts "\nFeature branches:" unless fbs.empty?
208
- fbs.each do |name, br|
209
- next if $config["ignore"].member?(br[:local_branch]) || $config["ignore"].member?(br[:remote_branch])
210
- next if br[:ignore]
211
- local_only = br[:remote_branch].nil?
212
- remote_only = br[:local_branch].nil?
213
- name = if local_only
214
- purple br[:name]
215
- elsif remote_only
216
- cyan br[:name]
217
- else
218
- green br[:name]
219
- end
220
-
221
- ## for remote_only branches, we'll compute wrt the remote branch head. otherwise, we'll
222
- ## use the local branch head.
223
- head = remote_only ? br[:remote_branch] : br[:local_branch]
224
-
225
- remote_ahead = b[:remote_branch] ? commits_between(b[:remote_branch], head) : []
226
- local_ahead = b[:local_branch] ? commits_between(b[:local_branch], head) : []
227
-
228
- if local_ahead.empty? && remote_ahead.empty?
229
- puts "#{widget true, remote_only, local_only} #{name} #{local_only ? "(local-only) " : ""}is merged in"
230
- elsif local_ahead.empty?
231
- puts "#{widget true, remote_only, local_only, true} #{name} merged in (only locally)"
232
- else
233
- behind = commits_between head, (br[:local_branch] || br[:remote_branch])
234
- ahead = remote_only ? remote_ahead : local_ahead
235
- puts "#{widget false, remote_only, local_only} #{name} #{local_only ? "(local-only) " : ""}is NOT merged in (#{ahead_behind_string ahead, behind})"
236
- show_commits ahead unless $short
237
- end
238
- end
239
- else
240
- puts "\nIntegration branches:" unless ibs.empty? # unlikely
241
- ibs.sort_by { |v, br| v }.each do |v, br|
242
- next if $config["ignore"].member?(br[:local_branch]) || $config["ignore"].member?(br[:remote_branch])
243
- next if br[:ignore]
244
- local_only = br[:remote_branch].nil?
245
- remote_only = br[:local_branch].nil?
246
- name = remote_only ? cyan(br[:name]) : green(br[:name])
247
-
248
- ahead = commits_between v, (b[:local_branch] || b[:remote_branch])
249
- if ahead.empty?
250
- puts "#{widget true, local_only} merged into #{name}"
251
- else
252
- #behind = commits_between b[:local_branch], v
253
- puts "#{widget false, local_only} NOT merged into #{name} (#{ahead.size.pluralize 'commit'} ahead)"
254
- show_commits ahead unless $short
255
- end
256
- end
257
- end
258
- end
259
-
260
- #### EXECUTION STARTS HERE ####
261
-
262
- ## find config file and load it
263
- $config = { "integration-branches" => %w(heads/master heads/next heads/edge), "ignore" => [], "max_commits" => 5 }.merge begin
264
- fn = find_file CONFIG_FN
265
- if fn && (h = YAML::load_file(fn)) # yaml turns empty files into false
266
- h["integration-branches"] ||= h["versions"] # support old nomenclature
267
- h
268
- else
269
- {}
270
- end
271
- end
272
-
273
- if $dump_config
274
- puts $config.to_yaml
275
- exit
276
- end
277
-
278
- ## first, index registered remotes
279
- remotes = `git config --get-regexp ^remote\.\*\.url`.split(/[\r\n]+/).inject({}) do |hash, l|
280
- l =~ /^remote\.(.+?)\.url (.+)$/ or next hash
281
- hash[$1] ||= $2
282
- hash
283
- end
284
-
285
- ## next, index followed branches
286
- branches = `git config --get-regexp ^branch\.`.split(/[\r\n]+/).inject({}) do |hash, l|
287
- case l
288
- when /branch\.(.*?)\.remote (.+)/
289
- name, remote = $1, $2
290
-
291
- hash[name] ||= {}
292
- hash[name].merge! :remote => remote, :remote_url => remotes[remote]
293
- when /branch\.(.*?)\.merge ((refs\/)?heads\/)?(.+)/
294
- name, remote_branch = $1, $4
295
- hash[name] ||= {}
296
- hash[name].merge! :remote_mergepoint => remote_branch
297
- end
298
- hash
299
- end
300
-
301
- ## finally, index all branches
302
- remote_branches = {}
303
- `git show-ref`.split(/[\r\n]+/).each do |l|
304
- sha1, ref = l.chomp.split " refs/"
305
-
306
- if ref =~ /^heads\/(.+)$/ # local branch
307
- name = $1
308
- next if name == "HEAD"
309
- branches[name] ||= {}
310
- branches[name].merge! :name => name, :local_branch => ref
311
- elsif ref =~ /^remotes\/(.+?)\/(.+)$/ # remote branch
312
- remote, name = $1, $2
313
- remote_branches["#{remote}/#{name}"] = true
314
- next if name == "HEAD"
315
- ignore = !($all || remote == "origin")
316
-
317
- branch = name
318
- if branches[name] && branches[name][:remote] == remote
319
- # nothing
320
- else
321
- name = "#{remote}/#{branch}"
322
- end
323
-
324
- branches[name] ||= {}
325
- branches[name].merge! :name => name, :remote => remote, :remote_branch => "#{remote}/#{branch}", :remote_url => remotes[remote], :ignore => ignore
326
- end
327
- end
328
-
329
- ## assemble remotes
330
- branches.each do |k, b|
331
- next unless b[:remote] && b[:remote_mergepoint]
332
- b[:remote_branch] = if b[:remote] == "."
333
- b[:remote_mergepoint]
334
- else
335
- t = "#{b[:remote]}/#{b[:remote_mergepoint]}"
336
- remote_branches[t] && t # only if it's still alive
337
- end
338
- end
339
-
340
- show_dirty = ARGV.empty?
341
- targets = if ARGV.empty?
342
- [`git symbolic-ref HEAD`.chomp.sub(/^refs\/heads\//, "")]
343
- else
344
- ARGV.map { |x| x.sub(/^heads\//, "") }
345
- end.map { |t| branches[t] or abort "Error: can't find branch #{t.inspect}." }
346
-
347
- targets.each do |t|
348
- show t
349
- show_relations t, branches if $show_relations || t[:remote_branch].nil?
350
- end
351
-
352
- modified = show_dirty && `git ls-files -m` != ""
353
- uncommitted = show_dirty && `git diff-index --cached HEAD` != ""
354
-
355
- if $key
356
- puts
357
- puts KEY
358
- end
359
-
360
- puts if modified || uncommitted
361
- puts "#{red "NOTE"}: working directory contains modified files." if modified
362
- puts "#{red "NOTE"}: staging area contains staged but uncommitted files." if uncommitted
363
-
364
- # the end!
@@ -1,24 +0,0 @@
1
- require 'English'
2
-
3
- module Thegarage
4
- module Gitx
5
- class Runner
6
- attr_accessor :shell, :options
7
-
8
- def initialize(shell, options = {})
9
- @shell = shell
10
- @options = options
11
- end
12
-
13
- # execute a shell command and raise an error if non-zero exit code is returned
14
- # return the string output from the command
15
- def run_cmd(cmd, options = {})
16
- shell.say "$ #{cmd}"
17
- output = `#{cmd}`
18
- success = $CHILD_STATUS.to_i == 0
19
- fail "#{cmd} failed" unless success || options[:allow_failure]
20
- output
21
- end
22
- end
23
- end
24
- end