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 +4 -4
- data/Guardfile +1 -1
- data/lib/thegarage/gitx/cli/base_command.rb +0 -2
- data/lib/thegarage/gitx/cli/integrate_command.rb +1 -1
- data/lib/thegarage/gitx/cli/review_command.rb +64 -78
- data/lib/thegarage/gitx/version.rb +1 -1
- data/spec/fixtures/vcr_cassettes/pull_request_does_exist.yml +81 -0
- data/spec/fixtures/vcr_cassettes/pull_request_does_not_exist.yml +65 -0
- data/spec/support/stub_execution.rb +7 -0
- data/spec/support/vcr.rb +6 -0
- data/spec/thegarage/gitx/cli/integrate_command_spec.rb +2 -2
- data/spec/thegarage/gitx/cli/review_command_spec.rb +33 -53
- data/thegarage-gitx.gemspec +1 -1
- metadata +26 -21
- data/bin/git-wtf +0 -364
- data/lib/thegarage/gitx/runner.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0b14ee5697a2883977afed2c228ab55bffb1465
|
4
|
+
data.tar.gz: aef6ef947211f421c07b0e3aef71da696c2e9c31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0dad8dba2b0b25ef97c4cd0a1628ba88227270ed2ce68de7cc53777206e762c90ce3e6833bd7ea509b0f8ef8f227d33364b8cb38c94fb364bf7e9baad63ada2f
|
7
|
+
data.tar.gz: 4ddd4bd7aa598289b85962a2d6065ce40a242090fdb8084382bea447c9e20041e19299d39781453bdf9b5010b51a83fefbbc86ca083fcb7b464b5473940239a4
|
data/Guardfile
CHANGED
@@ -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
|
@@ -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 =
|
33
|
-
if
|
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
|
33
|
+
run_cmd "open #{pull_request.html_url}" if options[:open]
|
43
34
|
end
|
44
35
|
|
45
36
|
private
|
46
37
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
# @
|
118
|
-
# @
|
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 =
|
117
|
+
head_reference = "#{repo_organization_name}:#{branch}"
|
121
118
|
params = {
|
122
119
|
head: head_reference,
|
123
120
|
state: 'open'
|
124
121
|
}
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
-
# @
|
151
|
-
# @
|
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
|
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'
|
@@ -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
|
data/spec/support/vcr.rb
ADDED
@@ -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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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/
|
91
|
+
stub_request(:patch, 'https://api.github.com/repos/thegarage/thegarage-gitx/issues/10').to_return(:status => 200)
|
88
92
|
|
89
|
-
|
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/
|
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
|
-
|
106
|
-
expect(cli).to receive(:
|
107
|
-
|
108
|
-
|
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
|
data/thegarage-gitx.gemspec
CHANGED
@@ -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.
|
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-
|
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:
|
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
|