thegarage-gitx 2.2.2 → 2.2.3.pre1

Sign up to get free protection for your applications and to get access to all the features.
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