git_reflow 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,12 +2,16 @@ module GitReflow
2
2
  module Config
3
3
  extend self
4
4
 
5
- def get(key)
6
- if cached_key_value = instance_variable_get(:"@#{key.tr('.-', '_')}")
5
+ def get(key, options = {reload: false})
6
+ if options[:reload] == false and cached_key_value = instance_variable_get(:"@#{key.tr('.-', '_')}")
7
7
  cached_key_value
8
8
  else
9
- new_value = GitReflow::Sandbox.run "git config --get #{key}", loud: false
10
- instance_variable_set(:"@#{key.tr('.-', '_')}", new_value)
9
+ if options[:all]
10
+ new_value = GitReflow::Sandbox.run "git config --get-all #{key}", loud: false
11
+ else
12
+ new_value = GitReflow::Sandbox.run "git config --get #{key}", loud: false
13
+ end
14
+ instance_variable_set(:"@#{key.tr('.-', '_')}", new_value.strip)
11
15
  end
12
16
  end
13
17
 
@@ -19,5 +23,22 @@ module GitReflow
19
23
  GitReflow::Sandbox.run "git config --global --replace-all #{key} \"#{value}\"", loud: false
20
24
  end
21
25
  end
26
+
27
+ def unset(key, options = { local: false })
28
+ value = (options[:value].nil?) ? "" : "\"#{options[:value]}\""
29
+ if options.delete(:local)
30
+ GitReflow::Sandbox.run "git config --unset #{key} #{value}", loud: false
31
+ else
32
+ GitReflow::Sandbox.run "git config --global --unset #{key} #{value}", loud: false
33
+ end
34
+ end
35
+
36
+ def add(key, value, options = { local: false })
37
+ if options.delete(:local)
38
+ GitReflow::Sandbox.run "git config --add #{key} \"#{value}\"", loud: false
39
+ else
40
+ GitReflow::Sandbox.run "git config --global --add #{key} \"#{value}\"", loud: false
41
+ end
42
+ end
22
43
  end
23
44
  end
@@ -2,12 +2,27 @@ require 'git_reflow/config'
2
2
 
3
3
  module GitReflow
4
4
  class GitServer::Base
5
- @@connection = nil
6
- @@project_only = false
5
+ extend GitHelpers
7
6
 
8
- def initialize(options)
9
- @@project_only = !!options.delete(:project_only)
7
+ @@connection = nil
8
+
9
+ class PullRequest
10
+ attr_accessor :description, :html_url, :feature_branch_name, :base_branch_name, :build_status, :source_object
10
11
 
12
+ def initialize(attributes)
13
+ raise "PullRequest#initialize must be implemented"
14
+ end
15
+
16
+ def method_missing(method_sym, *arguments, &block)
17
+ if source_object and source_object.respond_to? method_sym
18
+ source_object.send method_sym
19
+ else
20
+ super
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize(options)
11
26
  site_url = self.class.site_url
12
27
  api_endpoint = self.class.api_endpoint
13
28
 
@@ -17,14 +32,6 @@ module GitReflow
17
32
  authenticate
18
33
  end
19
34
 
20
- def authenticate
21
- raise "#{self.class.to_s}#authenticate method must be implemented"
22
- end
23
-
24
- def find_pull_request(options)
25
- raise "#{self.class.to_s}#find_pull_request(options) method must be implemented"
26
- end
27
-
28
35
  def self.connection
29
36
  raise "#{self.class.to_s}.connection method must be implemented"
30
37
  end
@@ -33,14 +40,6 @@ module GitReflow
33
40
  raise "#{self.class.to_s}.user method must be implemented"
34
41
  end
35
42
 
36
- def self.oauth_token
37
- raise "#{self.class.to_s}.oauth_token method must be implemented"
38
- end
39
-
40
- def self.oauth_token=(oauth_token)
41
- raise "#{self.class.to_s}.oauth_token= method must be implemented"
42
- end
43
-
44
43
  def self.api_endpoint
45
44
  raise "#{self.class.to_s}.api_endpoint method must be implemented"
46
45
  end
@@ -57,10 +56,22 @@ module GitReflow
57
56
  raise "#{self.class.to_s}.site_url= method must be implemented"
58
57
  end
59
58
 
59
+ def self.project_only?
60
+ GitReflow::Config.get("reflow.local-projects", all: true).include? "#{remote_user}/#{remote_repo_name}"
61
+ end
62
+
60
63
  def connection
61
64
  @connection ||= self.class.connection
62
65
  end
63
66
 
67
+ def authenticate
68
+ raise "#{self.class.to_s}#authenticate method must be implemented"
69
+ end
70
+
71
+ def find_open_pull_request(options)
72
+ raise "#{self.class.to_s}#find_open_pull_request(options) method must be implemented"
73
+ end
74
+
64
75
  def pull_request_comments(pull_request)
65
76
  raise "#{self.class.to_s}#pull_request_comments(pull_request) method must be implemented"
66
77
  end
@@ -69,6 +80,10 @@ module GitReflow
69
80
  pull_request_comments(pull_request).count > 0
70
81
  end
71
82
 
83
+ def last_comment_for_pull_request(pull_request)
84
+ raise "#{self.class.to_s}#last_comment_for_pull_request(pull_request) method must be implemented"
85
+ end
86
+
72
87
  def get_build_status sha
73
88
  raise "#{self.class.to_s}#get_build_status(sha) method must be implemented"
74
89
  end
@@ -77,12 +92,16 @@ module GitReflow
77
92
  raise "#{self.class.to_s}#colorized_build_description(status) method must be implemented"
78
93
  end
79
94
 
80
- def find_authors_of_open_pull_request_comments(pull_request)
81
- raise "#{self.class.to_s}#find_authors_of_open_pull_request_comments(pull_request) method must be implemented"
95
+ def reviewers(pull_request)
96
+ raise "#{self.class.to_s}#reviewers(pull_request) method must be implemented"
97
+ end
98
+
99
+ def approvals(pull_request)
100
+ raise "#{self.class.to_s}#approvals(pull_request) method must be implemented"
82
101
  end
83
102
 
84
- def get_commited_time(commit_sha)
85
- raise "#{self.class.to_s}#get_commited_time(commit_sha) method must be implemented"
103
+ def reviewers_pending_response(pull_request)
104
+ reviewers(pull_request) - approvals(pull_request)
86
105
  end
87
106
  end
88
107
  end
@@ -0,0 +1,166 @@
1
+ require 'bitbucket_rest_api'
2
+ require 'git_reflow/git_helpers'
3
+
4
+ module GitReflow
5
+ module GitServer
6
+ class BitBucket < Base
7
+
8
+ class PullRequest < Base::PullRequest
9
+ def initialize(attributes)
10
+ self.description = attributes.description
11
+ self.source_object = attributes
12
+ self.number = attributes.id
13
+ self.html_url = "#{attributes.source.repository.links.html.href}/pull-request/#{self.number}"
14
+ self.feature_branch_name = attributes.source.branch.name
15
+ self.base_branch_name = attributes.destination.branch.name
16
+ self.build_status = nil
17
+ end
18
+ end
19
+
20
+ attr_accessor :connection
21
+
22
+ def initialize(config_options = {})
23
+ project_only = !!config_options.delete(:project_only)
24
+
25
+ if project_only
26
+ GitReflow::Config.add('reflow.local-projects', "#{self.class.remote_user}/#{self.class.remote_repo_name}")
27
+ GitReflow::Config.set('reflow.git-server', 'BitBucket', local: true)
28
+ else
29
+ GitReflow::Config.unset('reflow.local-projects', value: "#{self.class.remote_user}/#{self.class.remote_repo_name}")
30
+ GitReflow::Config.set('reflow.git-server', 'BitBucket')
31
+ end
32
+ end
33
+
34
+ def self.connection
35
+ if api_key_setup?
36
+ @connection ||= ::BitBucket.new login: remote_user, password: api_key
37
+ end
38
+ end
39
+
40
+ def self.api_endpoint
41
+ endpoint = GitReflow::Config.get("bitbucket.endpoint")
42
+ (endpoint.length > 0) ? endpoint : ::BitBucket::Configuration::DEFAULT_ENDPOINT
43
+ end
44
+
45
+ def self.site_url
46
+ site_url = GitReflow::Config.get("bitbucket.site")
47
+ (site_url.length > 0) ? site_url : 'https://bitbucket.org'
48
+ end
49
+
50
+ def self.api_key
51
+ GitReflow::Config.get("bitbucket.api-key", reload: true)
52
+ end
53
+
54
+ def self.api_key=(key)
55
+ GitReflow::Config.set("bitbucket.api-key", key, local: project_only?)
56
+ end
57
+ def self.api_key_setup?
58
+ (self.api_key.length > 0)
59
+ end
60
+
61
+ def self.user
62
+ GitReflow::Config.get('bitbucket.user')
63
+ end
64
+
65
+ def self.user=(bitbucket_user)
66
+ GitReflow::Config.set('bitbucket.user', bitbucket_user, local: project_only?)
67
+ end
68
+
69
+ def authenticate(options = {silent: false})
70
+ begin
71
+ if connection and self.class.api_key_setup?
72
+ unless options[:silent]
73
+ puts "\nYour BitBucket account was already setup with:"
74
+ puts "\tUser Name: #{self.class.user}"
75
+ end
76
+ else
77
+ self.class.user = options[:user] || ask("Please enter your BitBucket username: ")
78
+ puts "\nIn order to connect your BitBucket account,"
79
+ puts "you'll need to generate an API key for your team"
80
+ puts "Visit #{self.class.site_url}/account/user/#{self.class.remote_user}/api-key/, to generate it\n"
81
+ self.class.api_key = ask("Please enter your team's API key: ")
82
+ connection.repos.all(self.class.remote_user).count
83
+ self.class.say "Connected to BitBucket\!", :success
84
+ end
85
+ rescue ::BitBucket::Error::Unauthorized => e
86
+ GitReflow::Config.unset('bitbucket.api-key', local: self.class.project_only?)
87
+ self.class.say "Invalid API key for team #{self.class.remote_user}.", :error
88
+ end
89
+ end
90
+
91
+ def connection
92
+ @connection ||= self.class.connection
93
+ end
94
+
95
+ def create_pull_request(options = {})
96
+ PullRequest.new connection.repos.pull_requests.create(self.class.remote_user, self.class.remote_repo_name,
97
+ title: options[:title],
98
+ body: options[:body],
99
+ source: {
100
+ branch: { name: self.class.current_branch },
101
+ repository: { full_name: "#{self.class.remote_user}/#{self.class.remote_repo_name}" }
102
+ },
103
+ destination: {
104
+ branch: { name: options[:base] }
105
+ },
106
+ reviewers: [username: self.class.user])
107
+ end
108
+
109
+ def find_open_pull_request(options = {})
110
+ begin
111
+ matching_pull = connection.repos.pull_requests.all(self.class.remote_user, self.class.remote_repo_name, limit: 1).select do |pr|
112
+ pr.source.branch.name == options[:from] and
113
+ pr.destination.branch.name == options[:to]
114
+ end.first
115
+
116
+ if matching_pull
117
+ PullRequest.new matching_pull
118
+ end
119
+ rescue ::BitBucket::Error::NotFound => e
120
+ self.class.say "No BitBucket repo found for #{self.class.remote_user}/#{self.class.remote_repo_name}", :error
121
+ rescue ::BitBucket::Error::Forbidden => e
122
+ self.class.say "You don't have API access to this repo", :error
123
+ end
124
+ end
125
+
126
+ def pull_request_comments(pull_request)
127
+ connection.repos.pull_requests.comments.all(self.class.remote_user, self.class.remote_repo_name, pull_request.id)
128
+ end
129
+
130
+ def last_comment_for_pull_request(pull_request)
131
+ last_comment = pull_request_comments(pull_request).first
132
+ return "" unless last_comment
133
+ "#{pull_request_comments(pull_request).first.content.raw}"
134
+ end
135
+
136
+ def get_build_status sha
137
+ # BitBucket does not currently support build status via API
138
+ # for updates: https://bitbucket.org/site/master/issue/8548/better-ci-integration-add-a-build-status
139
+ return nil
140
+ end
141
+
142
+ def colorized_build_description status
143
+ ""
144
+ end
145
+
146
+ def reviewers(pull_request)
147
+ comments = pull_request_comments(pull_request)
148
+
149
+ return [] unless comments.size > 0
150
+ comments.map {|c| c.user.username } - [self.class.user]
151
+ end
152
+
153
+ def approvals(pull_request)
154
+ approved = []
155
+
156
+ connection.repos.pull_requests.activity(self.class.remote_user, self.class.remote_repo_name, pull_request.id).each do |activity|
157
+ break unless activity.respond_to?(:approval) and activity.approval.user.username != self.class.user
158
+ approved |= [activity.approval.user.username]
159
+ end
160
+
161
+ approved
162
+ end
163
+
164
+ end
165
+ end
166
+ end
@@ -4,21 +4,29 @@ require 'git_reflow/git_helpers'
4
4
  module GitReflow
5
5
  module GitServer
6
6
  class GitHub < Base
7
- include GitHelpers
7
+ extend GitHelpers
8
+
9
+ class PullRequest < Base::PullRequest
10
+ def initialize(attributes)
11
+ self.description = attributes.body
12
+ self.html_url = attributes.html_url
13
+ self.feature_branch_name = attributes.head.label
14
+ self.base_branch_name = attributes.base.label
15
+ self.build_status = attributes.head.sha
16
+ self.source_object = attributes
17
+ end
18
+ end
8
19
 
9
20
  attr_accessor :connection
10
21
 
11
- @@project_only = false
12
- @@using_enterprise = false
13
-
14
22
  def initialize(config_options = {})
15
- @@project_only = !!config_options.delete(:project_only)
16
- @@using_enterprise = !!config_options.delete(:enterprise)
23
+ project_only = !!config_options.delete(:project_only)
24
+ using_enterprise = !!config_options.delete(:enterprise)
17
25
 
18
26
  gh_site_url = self.class.site_url
19
27
  gh_api_endpoint = self.class.api_endpoint
20
-
21
- if @@using_enterprise
28
+
29
+ if using_enterprise
22
30
  gh_site_url = ask("Please enter your Enterprise site URL (e.g. https://github.company.com):")
23
31
  gh_api_endpoint = ask("Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):")
24
32
  end
@@ -26,13 +34,66 @@ module GitReflow
26
34
  self.class.site_url = gh_site_url
27
35
  self.class.api_endpoint = gh_api_endpoint
28
36
 
29
- if @@project_only
37
+ if project_only
38
+ GitReflow::Config.add('reflow.local-projects', "#{self.class.remote_user}/#{self.class.remote_repo_name}")
30
39
  GitReflow::Config.set('reflow.git-server', 'GitHub', local: true)
31
40
  else
41
+ GitReflow::Config.unset('reflow.local-projects', value: "#{self.class.remote_user}/#{self.class.remote_repo_name}")
32
42
  GitReflow::Config.set('reflow.git-server', 'GitHub')
33
43
  end
34
44
  end
35
45
 
46
+ def self.connection
47
+ if self.oauth_token.length > 0
48
+ @connection ||= ::Github.new do |config|
49
+ config.oauth_token = GitServer::GitHub.oauth_token
50
+ config.endpoint = GitServer::GitHub.api_endpoint
51
+ config.site = GitServer::GitHub.site_url
52
+ end
53
+ end
54
+ end
55
+
56
+ def self.user
57
+ GitReflow::Config.get('github.user')
58
+ end
59
+
60
+ def self.user=(github_user)
61
+ GitReflow::Config.set('github.user', github_user, local: project_only?)
62
+ end
63
+
64
+ def self.oauth_token
65
+ GitReflow::Config.get('github.oauth-token')
66
+ end
67
+
68
+ def self.oauth_token=(oauth_token)
69
+ GitReflow::Config.set('github.oauth-token', oauth_token, local: project_only?)
70
+ oauth_token
71
+ end
72
+
73
+ def self.api_endpoint
74
+ endpoint = "#{GitReflow::Config.get('github.endpoint')}".strip
75
+ (endpoint.length > 0) ? endpoint : ::Github.endpoint
76
+ end
77
+
78
+ def self.api_endpoint=(api_endpoint)
79
+ GitReflow::Config.set("github.endpoint", api_endpoint, local: project_only?)
80
+ api_endpoint
81
+ end
82
+
83
+ def self.site_url
84
+ site_url = "#{GitReflow::Config.get('github.site')}".strip
85
+ (site_url.length > 0) ? site_url : ::Github.site
86
+ end
87
+
88
+ def self.site_url=(site_url)
89
+ GitReflow::Config.set("github.site", site_url, local: project_only?)
90
+ site_url
91
+ end
92
+
93
+ def connection
94
+ @connection ||= self.class.connection
95
+ end
96
+
36
97
  def authenticate(options = {silent: false})
37
98
  if connection and self.class.oauth_token.length > 0
38
99
  unless options[:silent]
@@ -82,77 +143,42 @@ module GitReflow
82
143
  end
83
144
 
84
145
  def create_pull_request(options = {})
85
- pull_request = connection.pull_requests.create(remote_user, remote_repo_name,
146
+ pull_request = connection.pull_requests.create(self.class.remote_user, self.class.remote_repo_name,
86
147
  title: options[:title],
87
148
  body: options[:body],
88
- head: "#{remote_user}:#{current_branch}",
149
+ head: "#{self.class.remote_user}:#{self.class.current_branch}",
89
150
  base: options[:base])
90
151
  end
91
152
 
92
- def find_pull_request(options = {})
93
- connection.pull_requests.all(remote_user, remote_repo_name, base: options[:to], head: "#{remote_user}:#{options[:from]}", :state => 'open').first
94
- end
95
-
96
- def self.connection
97
- if self.oauth_token.length > 0
98
- @connection ||= ::Github.new do |config|
99
- config.oauth_token = GitServer::GitHub.oauth_token
100
- config.endpoint = GitServer::GitHub.api_endpoint
101
- config.site = GitServer::GitHub.site_url
102
- end
153
+ def find_open_pull_request(options = {})
154
+ matching_pull = connection.pull_requests.all(self.class.remote_user, self.class.remote_repo_name, base: options[:to], head: "#{self.class.remote_user}:#{options[:from]}", :state => 'open').first
155
+ if matching_pull
156
+ PullRequest.new matching_pull
103
157
  end
104
158
  end
105
159
 
106
- def self.user
107
- GitReflow::Config.get('github.user')
160
+ def reviewers(pull_request)
161
+ comment_authors_for_pull_request(pull_request)
108
162
  end
109
163
 
110
- def self.user=(github_user)
111
- GitReflow::Config.set('github.user', github_user, local: @@project_only)
112
- end
113
-
114
- def self.oauth_token
115
- GitReflow::Config.get('github.oauth-token')
116
- end
117
-
118
- def self.oauth_token=(oauth_token)
119
- GitReflow::Config.set('github.oauth-token', oauth_token, local: @@project_only)
120
- oauth_token
121
- end
122
-
123
- def self.api_endpoint
124
- endpoint = GitReflow::Config.get('github.endpoint')
125
- (endpoint.length > 0) ? endpoint : ::Github.endpoint
126
- end
127
-
128
- def self.api_endpoint=(api_endpoint)
129
- GitReflow::Config.set("github.endpoint", api_endpoint, local: @@project_only)
130
- api_endpoint
131
- end
132
-
133
- def self.site_url
134
- site_url = GitReflow::Config.get('github.site')
135
- (site_url.length > 0) ? site_url : ::Github.site
136
- end
137
-
138
- def self.site_url=(site_url)
139
- GitReflow::Config.set("github.site", site_url, local: @@project_only)
140
- site_url
141
- end
142
-
143
- def connection
144
- @connection ||= self.class.connection
164
+ def approvals(pull_request)
165
+ pull_last_committed_at = get_commited_time(pull_request.head.sha)
166
+ lgtm_authors = comment_authors_for_pull_request(pull_request, :with => LGTM, :after => pull_last_committed_at)
145
167
  end
146
168
 
147
169
  def pull_request_comments(pull_request)
148
- comments = connection.issues.comments.all remote_user, remote_repo_name, number: pull_request.number
149
- review_comments = connection.pull_requests.comments.all remote_user, remote_repo_name, number: pull_request.number
170
+ comments = connection.issues.comments.all self.class.remote_user, self.class.remote_repo_name, number: pull_request.number
171
+ review_comments = connection.pull_requests.comments.all self.class.remote_user, self.class.remote_repo_name, number: pull_request.number
150
172
 
151
173
  review_comments.to_a + comments.to_a
152
174
  end
153
175
 
176
+ def last_comment_for_pull_request(pull_request)
177
+ "#{pull_request_comments(pull_request).last.body.inspect}"
178
+ end
179
+
154
180
  def get_build_status sha
155
- connection.repos.statuses.all(remote_user, remote_repo_name, sha).first
181
+ connection.repos.statuses.all(self.class.remote_user, self.class.remote_repo_name, sha).first
156
182
  end
157
183
 
158
184
  def colorized_build_description status
@@ -160,15 +186,6 @@ module GitReflow
160
186
  status.description.colorize( colorized_statuses[status.state.to_sym] )
161
187
  end
162
188
 
163
- def find_authors_of_open_pull_request_comments(pull_request)
164
- # first we'll gather all the authors that have commented on the pull request
165
- pull_last_committed_at = get_commited_time(pull_request.head.sha)
166
- comment_authors = comment_authors_for_pull_request(pull_request)
167
- lgtm_authors = comment_authors_for_pull_request(pull_request, :with => LGTM, :after => pull_last_committed_at)
168
-
169
- comment_authors - lgtm_authors
170
- end
171
-
172
189
  def comment_authors_for_pull_request(pull_request, options = {})
173
190
  all_comments = pull_request_comments(pull_request)
174
191
  comment_authors = []
@@ -185,7 +202,7 @@ module GitReflow
185
202
  end
186
203
 
187
204
  def get_commited_time(commit_sha)
188
- last_commit = connection.repos.commits.find remote_user, remote_repo_name, commit_sha
205
+ last_commit = connection.repos.commits.find self.class.remote_user, self.class.remote_repo_name, commit_sha
189
206
  Time.parse last_commit.commit.author[:date]
190
207
  end
191
208