suse-git-pulls 0.3.5

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.
Files changed (4) hide show
  1. data/LICENSE +20 -0
  2. data/bin/git-pulls +6 -0
  3. data/lib/git-pulls.rb +306 -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-pulls ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'git-pulls'
5
+
6
+ GitPulls.start(ARGV)
data/lib/git-pulls.rb ADDED
@@ -0,0 +1,306 @@
1
+ require 'json'
2
+ require 'launchy'
3
+ require 'octokit'
4
+
5
+ class GitPulls
6
+
7
+ PULLS_CACHE_FILE = '.git/pulls_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
+ GitPulls.new(args).run
17
+ end
18
+
19
+ def run
20
+ configure
21
+ if @command && self.respond_to?(@command)
22
+ # If the cache file doesn't exist, make sure we run update
23
+ # before any other command. git-pulls will otherwise crash
24
+ # with an exception.
25
+ update unless File.exists?(PULLS_CACHE_FILE) || @command == 'update'
26
+
27
+ self.send @command
28
+ elsif %w(-h --help).include?(@command)
29
+ usage
30
+ else
31
+ help
32
+ end
33
+ end
34
+
35
+ ## COMMANDS ##
36
+
37
+ def help
38
+ puts "No command: #{@command}"
39
+ puts "Try: update, list, show, merge, browse"
40
+ puts "or call with '-h' for usage information"
41
+ end
42
+
43
+ def usage
44
+ puts <<-USAGE
45
+ Usage: git pulls update
46
+ or: git pulls list [--reverse]
47
+ or: git pulls show <number> [--full]
48
+ or: git pulls browse <number>
49
+ or: git pulls merge <number>
50
+ USAGE
51
+ end
52
+
53
+ def merge
54
+ num = @args.shift
55
+ option = @args.shift
56
+ if p = pull_num(num)
57
+ if p['head']['repository']
58
+ o = p['head']['repository']['owner']
59
+ r = p['head']['repository']['name']
60
+ else # they deleted the source repo
61
+ o = p['head']['user']['login']
62
+ purl = p['patch_url']
63
+ puts "Sorry, #{o} deleted the source repository, git-pulls doesn't support this."
64
+ puts "You can manually patch your repo by running:"
65
+ puts
66
+ puts " curl #{purl} | git am"
67
+ puts
68
+ puts "Tell the contributor not to do this."
69
+ return false
70
+ end
71
+ s = p['head']['sha']
72
+
73
+ message = "Merge pull request ##{num} from #{o}/#{r}\n\n---\n\n"
74
+ message += p['body'].gsub("'", '')
75
+ cmd = ''
76
+ if option == '--log'
77
+ message += "\n\n---\n\nMerge Log:\n"
78
+ puts cmd = "git merge --no-ff --log -m '#{message}' #{s}"
79
+ else
80
+ puts cmd = "git merge --no-ff -m '#{message}' #{s}"
81
+ end
82
+ exec(cmd)
83
+ else
84
+ puts "No such number"
85
+ end
86
+ end
87
+
88
+ def show
89
+ num = @args.shift
90
+ option = @args.shift
91
+ if p = pull_num(num)
92
+ puts "Number : #{p['number']}"
93
+ puts "Label : #{p['head']['label']}"
94
+ puts "Created : #{p['created_at']}"
95
+ puts "Votes : #{p['votes']}"
96
+ puts "Comments : #{p['comments']}"
97
+ puts
98
+ puts "Title : #{p['title']}"
99
+ puts "Body :"
100
+ puts
101
+ puts p['body']
102
+ puts
103
+ puts '------------'
104
+ puts
105
+ if option == '--full'
106
+ exec "git diff --color=always HEAD...#{p['head']['sha']}"
107
+ else
108
+ puts "cmd: git diff HEAD...#{p['head']['sha']}"
109
+ puts git("diff --stat --color=always HEAD...#{p['head']['sha']}")
110
+ end
111
+ else
112
+ puts "No such number"
113
+ end
114
+ end
115
+
116
+ def browse
117
+ num = @args.shift
118
+ if p = pull_num(num)
119
+ Launchy.open(p['html_url'])
120
+ else
121
+ puts "No such number"
122
+ end
123
+ end
124
+
125
+ def list
126
+ option = @args.shift
127
+ puts "Open Pull Requests for #{@user}/#{@repo}"
128
+ pulls = get_pull_info
129
+ pulls.reverse! if option == '--reverse'
130
+ count = 0
131
+ pulls.each do |pull|
132
+ line = []
133
+ line << l(pull['number'], 4)
134
+ line << l(Date.parse(pull['created_at']).strftime("%m/%d"), 5)
135
+ line << l(pull['comments'], 2)
136
+ line << l(pull['title'], 35)
137
+ line << l(pull['head']['label'], 20)
138
+ sha = pull['head']['sha']
139
+ if not_merged?(sha)
140
+ puts line.join ' '
141
+ count += 1
142
+ end
143
+ end
144
+ if count == 0
145
+ puts ' -- no open pull requests --'
146
+ end
147
+ end
148
+
149
+ def update
150
+ puts "Updating #{@user}/#{@repo}"
151
+ cache_pull_info
152
+ fetch_stale_forks
153
+ list
154
+ end
155
+
156
+ def fetch_stale_forks
157
+ puts "Checking for forks in need of fetching"
158
+ pulls = get_pull_info
159
+ repos = {}
160
+ pulls.each do |pull|
161
+ next if pull['head']['repository'].nil? # Fork has been deleted
162
+ o = pull['head']['repository']['owner']
163
+ r = pull['head']['repository']['name']
164
+ s = pull['head']['sha']
165
+ if !has_sha(s)
166
+ repo = "#{o}/#{r}"
167
+ repos[repo] = true
168
+ end
169
+ end
170
+ if github_credentials_provided?
171
+ endpoint = "git@github.com:"
172
+ else
173
+ endpoint = github_endpoint + "/"
174
+ end
175
+ repos.each do |repo, bool|
176
+ puts " fetching #{repo}"
177
+ git("fetch #{endpoint}#{repo}.git +refs/heads/*:refs/pr/#{repo}/*")
178
+ end
179
+ end
180
+
181
+ def has_sha(sha)
182
+ git("show #{sha} 2>&1")
183
+ $?.exitstatus == 0
184
+ end
185
+
186
+ def not_merged?(sha)
187
+ commits = git("rev-list #{sha} ^HEAD 2>&1")
188
+ commits.split("\n").size > 0
189
+ end
190
+
191
+ # DISPLAY HELPER FUNCTIONS #
192
+
193
+ def l(info, size)
194
+ clean(info)[0, size].ljust(size)
195
+ end
196
+
197
+ def r(info, size)
198
+ clean(info)[0, size].rjust(size)
199
+ end
200
+
201
+ def clean(info)
202
+ info.to_s.gsub("\n", ' ')
203
+ end
204
+
205
+ # PRIVATE REPOSITORIES ACCESS
206
+
207
+ def configure
208
+ Octokit.configure do |config|
209
+ config.login = github_login
210
+ config.token = github_token
211
+ config.endpoint = github_endpoint
212
+ end
213
+ end
214
+
215
+ def github_login
216
+ git("config --get-all github.user")
217
+ end
218
+
219
+ def github_token
220
+ git("config --get-all github.token")
221
+ end
222
+
223
+ def github_endpoint
224
+ host = git("config --get-all github.host")
225
+ if host.size > 0
226
+ host
227
+ else
228
+ 'https://github.com'
229
+ end
230
+ end
231
+
232
+ # API/DATA HELPER FUNCTIONS #
233
+
234
+ def github_credentials_provided?
235
+ if github_token.empty? && github_login.empty?
236
+ return false
237
+ end
238
+ true
239
+ end
240
+
241
+ def get_pull_info
242
+ get_data(PULLS_CACHE_FILE)['pulls']
243
+ end
244
+
245
+ def get_data(file)
246
+ data = JSON.parse(File.read(file))
247
+ end
248
+
249
+ def cache_pull_info
250
+ response = Octokit.pull_requests("#{@user}/#{@repo}")
251
+ save_data({'pulls' => response}, PULLS_CACHE_FILE)
252
+ end
253
+
254
+ def save_data(data, file)
255
+ File.open(file, "w+") do |f|
256
+ f.puts data.to_json
257
+ end
258
+ end
259
+
260
+ def pull_num(num)
261
+ data = get_pull_info
262
+ data.select { |p| p['number'].to_s == num.to_s }.first
263
+ end
264
+
265
+ def github_insteadof_matching(c, u)
266
+ first = c.collect {|k,v| [v, /url\.(.*github\.com.*)\.insteadof/.match(k)]}.
267
+ find {|v,m| u.index(v) and m != nil}
268
+ if first
269
+ return first[0], first[1][1]
270
+ end
271
+ return nil, nil
272
+ end
273
+
274
+ def github_user_and_proj(u)
275
+ # Trouble getting optional ".git" at end to work, so put that logic below
276
+ m = /github\.com.(.*?)\/(.*)/.match(u)
277
+ if m
278
+ return m[1], m[2].sub(/\.git\Z/, "")
279
+ end
280
+ return nil, nil
281
+ end
282
+
283
+ def repo_info
284
+ c = {}
285
+ config = git('config --list')
286
+ config.split("\n").each do |line|
287
+ k, v = line.split('=')
288
+ c[k] = v
289
+ end
290
+ u = c['remote.origin.url']
291
+
292
+ user, proj = github_user_and_proj(u)
293
+ if !(user and proj)
294
+ short, base = github_insteadof_matching(c, u)
295
+ if short and base
296
+ u = u.sub(short, base)
297
+ user, proj = github_user_and_proj(u)
298
+ end
299
+ end
300
+ [user, proj]
301
+ end
302
+
303
+ def git(command)
304
+ `git #{command}`.chomp
305
+ end
306
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: suse-git-pulls
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 5
10
+ version: 0.3.5
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-pulls facilitates github pull requests.
66
+ email: bamberger.dominik@gmail.com
67
+ executables:
68
+ - git-pulls
69
+ extensions: []
70
+
71
+ extra_rdoc_files: []
72
+
73
+ files:
74
+ - LICENSE
75
+ - lib/git-pulls.rb
76
+ - bin/git-pulls
77
+ has_rdoc: true
78
+ homepage: http://github.com/b4mboo/git-pulls
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 pull requests
111
+ test_files: []
112
+