git-review 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/LICENSE +20 -0
  2. data/bin/git-review +8 -0
  3. data/lib/git-review.rb +315 -0
  4. metadata +112 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Scott Chacon
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/bin/git-review ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+
4
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
5
+
6
+ require 'git-review'
7
+
8
+ GitReview.start(ARGV)
data/lib/git-review.rb ADDED
@@ -0,0 +1,315 @@
1
+ require 'json'
2
+ require 'launchy'
3
+ require 'octokit'
4
+
5
+ class GitReview
6
+
7
+ REVIEW_CACHE_FILE = '.git/review_cache.json'
8
+
9
+ def initialize(args)
10
+ @command = args.shift
11
+ @user, @repo = repo_info
12
+ @args = args
13
+ end
14
+
15
+ def self.start(args)
16
+ GitReview.new(args).run
17
+ end
18
+
19
+ def run
20
+ configure
21
+ if @command && self.respond_to?(@command)
22
+ update
23
+ self.send @command
24
+ elsif %w(-h --help).include?(@command)
25
+ usage
26
+ else
27
+ help
28
+ end
29
+ end
30
+
31
+ ## COMMANDS ##
32
+
33
+ def help
34
+ puts "No command: #{@command}"
35
+ puts "Try: update, list, show, merge, browse, create"
36
+ puts "or call with '-h' for usage information"
37
+ end
38
+
39
+ def usage
40
+ puts <<-USAGE
41
+ Usage: git review list [--reverse]
42
+ or: git review show <number> [--full]
43
+ or: git review browse <number>
44
+ or: git review merge <number>
45
+ or: git review create
46
+ USAGE
47
+ end
48
+
49
+ def merge
50
+ num = @args.shift
51
+ option = @args.shift
52
+ if p = pull_num(num)
53
+ if p['head']['repository']
54
+ o = p['head']['repository']['owner']
55
+ r = p['head']['repository']['name']
56
+ else # they deleted the source repo
57
+ o = p['head']['user']['login']
58
+ purl = p['patch_url']
59
+ puts "Sorry, #{o} deleted the source repository, git-review doesn't support this."
60
+ puts "You can manually patch your repo by running:"
61
+ puts
62
+ puts " curl #{purl} | git am"
63
+ puts
64
+ puts "Tell the contributor not to do this."
65
+ return false
66
+ end
67
+ s = p['head']['sha']
68
+
69
+ message = "Merge pull request ##{num} from #{o}/#{r}\n\n---\n\n"
70
+ message += p['body'].gsub("'", '')
71
+ cmd = ''
72
+ if option == '--log'
73
+ message += "\n\n---\n\nMerge Log:\n"
74
+ puts cmd = "git merge --no-ff --log -m '#{message}' #{s}"
75
+ else
76
+ puts cmd = "git merge --no-ff -m '#{message}' #{s}"
77
+ end
78
+ exec(cmd)
79
+ else
80
+ puts "No such number"
81
+ end
82
+ end
83
+
84
+ def show
85
+ num = @args.shift
86
+ option = @args.shift
87
+ if p = pull_num(num)
88
+ puts "Number : #{p['number']}"
89
+ puts "Label : #{p['head']['label']}"
90
+ puts "Created : #{p['created_at']}"
91
+ puts "Votes : #{p['votes']}"
92
+ puts "Comments : #{p['comments']}"
93
+ puts
94
+ puts "Title : #{p['title']}"
95
+ puts "Body :"
96
+ puts
97
+ puts p['body']
98
+ puts
99
+ puts '------------'
100
+ puts
101
+ if option == '--full'
102
+ exec "git diff --color=always HEAD...#{p['head']['sha']}"
103
+ else
104
+ puts "cmd: git diff HEAD...#{p['head']['sha']}"
105
+ puts git("diff --stat --color=always HEAD...#{p['head']['sha']}")
106
+ end
107
+ else
108
+ puts "No such number"
109
+ end
110
+ end
111
+
112
+ def browse
113
+ num = @args.shift
114
+ if p = pull_num(num)
115
+ Launchy.open(p['html_url'])
116
+ else
117
+ puts "No such number"
118
+ end
119
+ end
120
+
121
+ def list
122
+ option = @args.shift
123
+ puts "Open Pull Requests for #{@user}/#{@repo}"
124
+ pulls = get_pull_info
125
+ pulls.reverse! if option == '--reverse'
126
+ count = 0
127
+ pulls.each do |pull|
128
+ line = []
129
+ line << l(pull['number'], 4)
130
+ line << l(Date.parse(pull['created_at']).strftime("%m/%d"), 5)
131
+ line << l(pull['comments'], 2)
132
+ line << l(pull['title'], 35)
133
+ line << l(pull['head']['label'], 20)
134
+ sha = pull['head']['sha']
135
+ if not_merged?(sha)
136
+ puts line.join ' '
137
+ count += 1
138
+ end
139
+ end
140
+ if count == 0
141
+ puts ' -- no open pull requests --'
142
+ end
143
+ end
144
+
145
+ def update
146
+ cache_pull_info
147
+ fetch_stale_forks
148
+ end
149
+
150
+ def create
151
+ repo = "#{@user}/#{@repo}"
152
+ to_branch = 'master'
153
+ from_branch = get_from_branch_title
154
+ title = 'my title'
155
+ body = 'my body'
156
+
157
+ Octokit.create_pull_request(repo, to_branch, from_branch, title, body)
158
+ end
159
+
160
+ def get_from_branch_title
161
+ git('branch', false).match(/\*(.*)/)[0][2..-1]
162
+ end
163
+
164
+ def fetch_stale_forks
165
+ pulls = get_pull_info
166
+ repos = {}
167
+ pulls.each do |pull|
168
+ next if pull['head']['repository'].nil? # Fork has been deleted
169
+ o = pull['head']['repository']['owner']
170
+ r = pull['head']['repository']['name']
171
+ s = pull['head']['sha']
172
+ if !has_sha(s)
173
+ repo = "#{o}/#{r}"
174
+ repos[repo] = true
175
+ end
176
+ end
177
+ if github_credentials_provided?
178
+ endpoint = "git@github.com:"
179
+ else
180
+ endpoint = github_endpoint + "/"
181
+ end
182
+ repos.each do |repo, bool|
183
+ puts "fetching #{repo}"
184
+ git("fetch #{endpoint}#{repo}.git +refs/heads/*:refs/pr/#{repo}/*")
185
+ end
186
+ end
187
+
188
+ def has_sha(sha)
189
+ git("show #{sha} 2>&1")
190
+ $?.exitstatus == 0
191
+ end
192
+
193
+ def not_merged?(sha)
194
+ commits = git("rev-list #{sha} ^HEAD 2>&1")
195
+ commits.split("\n").size > 0
196
+ end
197
+
198
+ # DISPLAY HELPER FUNCTIONS #
199
+
200
+ def l(info, size)
201
+ clean(info)[0, size].ljust(size)
202
+ end
203
+
204
+ def r(info, size)
205
+ clean(info)[0, size].rjust(size)
206
+ end
207
+
208
+ def clean(info)
209
+ info.to_s.gsub("\n", ' ')
210
+ end
211
+
212
+ # PRIVATE REPOSITORIES ACCESS
213
+
214
+ def configure
215
+ Octokit.configure do |config|
216
+ config.login = github_login
217
+ config.token = github_token
218
+ config.endpoint = github_endpoint
219
+ end
220
+ end
221
+
222
+ def github_login
223
+ git("config --get-all github.user")
224
+ end
225
+
226
+ def github_token
227
+ git("config --get-all github.token")
228
+ end
229
+
230
+ def github_endpoint
231
+ host = git("config --get-all github.host")
232
+ if host.size > 0
233
+ host
234
+ else
235
+ 'https://github.com'
236
+ end
237
+ end
238
+
239
+ # API/DATA HELPER FUNCTIONS #
240
+
241
+ def github_credentials_provided?
242
+ if github_token.empty? && github_login.empty?
243
+ return false
244
+ end
245
+ true
246
+ end
247
+
248
+ def get_pull_info
249
+ get_data(REVIEW_CACHE_FILE)['review']
250
+ end
251
+
252
+ def get_data(file)
253
+ data = JSON.parse(File.read(file))
254
+ end
255
+
256
+ def cache_pull_info
257
+ response = Octokit.pull_requests("#{@user}/#{@repo}")
258
+ save_data({'review' => response}, REVIEW_CACHE_FILE)
259
+ end
260
+
261
+ def save_data(data, file)
262
+ File.open(file, "w+") do |f|
263
+ f.puts data.to_json
264
+ end
265
+ end
266
+
267
+ def pull_num(num)
268
+ data = get_pull_info
269
+ data.select { |p| p['number'].to_s == num.to_s }.first
270
+ end
271
+
272
+ def github_insteadof_matching(c, u)
273
+ first = c.collect {|k,v| [v, /url\.(.*github\.com.*)\.insteadof/.match(k)]}.
274
+ find {|v,m| u.index(v) and m != nil}
275
+ if first
276
+ return first[0], first[1][1]
277
+ end
278
+ return nil, nil
279
+ end
280
+
281
+ def github_user_and_proj(u)
282
+ # Trouble getting optional ".git" at end to work, so put that logic below
283
+ m = /github\.com.(.*?)\/(.*)/.match(u)
284
+ if m
285
+ return m[1], m[2].sub(/\.git\Z/, "")
286
+ end
287
+ return nil, nil
288
+ end
289
+
290
+ def repo_info
291
+ c = {}
292
+ config = git('config --list')
293
+ config.split("\n").each do |line|
294
+ k, v = line.split('=')
295
+ c[k] = v
296
+ end
297
+ u = c['remote.origin.url']
298
+
299
+ user, proj = github_user_and_proj(u)
300
+ if !(user and proj)
301
+ short, base = github_insteadof_matching(c, u)
302
+ if short and base
303
+ u = u.sub(short, base)
304
+ user, proj = github_user_and_proj(u)
305
+ end
306
+ end
307
+ [user, proj]
308
+ end
309
+
310
+ def git(command, chomp=true)
311
+ s = `git #{command}`
312
+ s.chomp! if chomp
313
+ s
314
+ end
315
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-review
3
+ version: !ruby/object:Gem::Version
4
+ hash: 13
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 4
9
+ - 1
10
+ version: 0.4.1
11
+ platform: ruby
12
+ authors:
13
+ - Cristian Messel, Dominik Bamberger
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-12 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: json
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: launchy
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: octokit
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - "="
56
+ - !ruby/object:Gem::Version
57
+ hash: 9
58
+ segments:
59
+ - 0
60
+ - 5
61
+ - 1
62
+ version: 0.5.1
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ description: git-review facilitates github code reviews.
66
+ email: bamberger.dominik@gmail.com
67
+ executables:
68
+ - git-review
69
+ extensions: []
70
+
71
+ extra_rdoc_files: []
72
+
73
+ files:
74
+ - LICENSE
75
+ - lib/git-review.rb
76
+ - bin/git-review
77
+ has_rdoc: true
78
+ homepage: http://github.com/b4mboo/git-review
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options: []
83
+
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project:
107
+ rubygems_version: 1.5.2
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: facilitates github code reviews
111
+ test_files: []
112
+