git-pulls-updated 0.3.5

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-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: []