git-pulls-updated 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 +300 -0
  4. metadata +96 -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,300 @@
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
+ repos.each do |repo, bool|
171
+ puts " fetching #{repo}"
172
+ git("fetch #{github_endpoint}/#{repo}.git +refs/heads/*:refs/pr/#{repo}/*")
173
+ end
174
+ end
175
+
176
+ def has_sha(sha)
177
+ git("show #{sha} 2>&1")
178
+ $?.exitstatus == 0
179
+ end
180
+
181
+ def not_merged?(sha)
182
+ commits = git("rev-list #{sha} ^HEAD 2>&1")
183
+ commits.split("\n").size > 0
184
+ end
185
+
186
+ # DISPLAY HELPER FUNCTIONS #
187
+
188
+ def l(info, size)
189
+ clean(info)[0, size].ljust(size)
190
+ end
191
+
192
+ def r(info, size)
193
+ clean(info)[0, size].rjust(size)
194
+ end
195
+
196
+ def clean(info)
197
+ info.to_s.gsub("\n", ' ')
198
+ end
199
+
200
+ # PRIVATE REPOSITORIES ACCESS
201
+
202
+ def configure
203
+ Octokit.configure do |config|
204
+ config.login = github_login
205
+ config.web_endpoint = github_endpoint
206
+ end
207
+ end
208
+
209
+ def github_login
210
+ git("config --get-all github.user")
211
+ end
212
+
213
+ def github_token
214
+ git("config --get-all github.token")
215
+ end
216
+
217
+ def github_endpoint
218
+ host = git("config --get-all github.host")
219
+ if host.size > 0
220
+ host
221
+ else
222
+ 'https://github.com'
223
+ end
224
+ end
225
+
226
+ # API/DATA HELPER FUNCTIONS #
227
+
228
+ def github_credentials_provided?
229
+ if github_token.empty? && github_login.empty?
230
+ return false
231
+ end
232
+ true
233
+ end
234
+
235
+ def get_pull_info
236
+ get_data(PULLS_CACHE_FILE)['pulls']
237
+ end
238
+
239
+ def get_data(file)
240
+ data = JSON.parse(File.read(file))
241
+ end
242
+
243
+ def cache_pull_info
244
+ response = Octokit.pull_requests("#{@user}/#{@repo}")
245
+ save_data({'pulls' => response}, PULLS_CACHE_FILE)
246
+ end
247
+
248
+ def save_data(data, file)
249
+ File.open(file, "w+") do |f|
250
+ f.puts data.to_json
251
+ end
252
+ end
253
+
254
+ def pull_num(num)
255
+ data = get_pull_info
256
+ data.select { |p| p['number'].to_s == num.to_s }.first
257
+ end
258
+
259
+ def github_insteadof_matching(c, u)
260
+ first = c.collect {|k,v| [v, /url\.(.*github\.com.*)\.insteadof/.match(k)]}.
261
+ find {|v,m| u.index(v) and m != nil}
262
+ if first
263
+ return first[0], first[1][1]
264
+ end
265
+ return nil, nil
266
+ end
267
+
268
+ def github_user_and_proj(u)
269
+ # Trouble getting optional ".git" at end to work, so put that logic below
270
+ m = /github\.com.(.*?)\/(.*)/.match(u)
271
+ if m
272
+ return m[1], m[2].sub(/\.git\Z/, "")
273
+ end
274
+ return nil, nil
275
+ end
276
+
277
+ def repo_info
278
+ c = {}
279
+ config = git('config --list')
280
+ config.split("\n").each do |line|
281
+ k, v = line.split('=')
282
+ c[k] = v
283
+ end
284
+ u = c['remote.origin.url']
285
+
286
+ user, proj = github_user_and_proj(u)
287
+ if !(user and proj)
288
+ short, base = github_insteadof_matching(c, u)
289
+ if short and base
290
+ u = u.sub(short, base)
291
+ user, proj = github_user_and_proj(u)
292
+ end
293
+ end
294
+ [user, proj]
295
+ end
296
+
297
+ def git(command)
298
+ `git #{command}`.chomp
299
+ end
300
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-pulls-updated
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.5
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Adrien Giboire
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: launchy
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: octokit
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: git-pulls facilitates github pull requests.
63
+ email: adrien.giboire@gmail.com
64
+ executables:
65
+ - git-pulls
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - LICENSE
70
+ - lib/git-pulls.rb
71
+ - bin/git-pulls
72
+ homepage: http://github.com/adriengiboire/git-pulls
73
+ licenses: []
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 1.8.24
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: facilitates github pull requests
96
+ test_files: []