hubeye 0.3.0 → 0.3.2
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.
- data/VERSION.rb +2 -2
- data/bin/hubeye +69 -67
- data/lib/hubeye/client/client.rb +83 -0
- data/lib/hubeye/client/connection.rb +44 -0
- data/lib/hubeye/config/environment.rb +13 -0
- data/lib/{config → hubeye/config}/parser.rb +1 -1
- data/lib/hubeye/helpers/time.rb +8 -0
- data/lib/{hooks → hubeye/hooks}/executer.rb +6 -5
- data/lib/{hooks → hubeye/hooks}/git_hooks.rb +2 -3
- data/lib/hubeye/log/logger.rb +39 -0
- data/lib/hubeye/notification/finder.rb +64 -0
- data/lib/hubeye/notification/gnomenotify.rb +15 -0
- data/lib/{notification → hubeye/notification}/growl.rb +0 -0
- data/lib/hubeye/server/commit.rb +51 -0
- data/lib/hubeye/server/server.rb +686 -0
- data/lib/hubeye/server/session.rb +59 -0
- data/lib/hubeye/shared/hubeye_protocol.rb +36 -0
- data/test/config_parser.rb +2 -2
- data/test/environment.rb +1 -1
- data/test/integration.rb +24 -0
- data/test/notification.rb +3 -2
- data/test/runner.rb +3 -3
- metadata +18 -13
- data/lib/client/hubeye_client.rb +0 -103
- data/lib/environment.rb +0 -10
- data/lib/helpers/time.rb +0 -5
- data/lib/log/logger.rb +0 -38
- data/lib/notification/finder.rb +0 -62
- data/lib/notification/gnomenotify.rb +0 -13
- data/lib/server/hubeye_server.rb +0 -767
data/lib/server/hubeye_server.rb
DELETED
@@ -1,767 +0,0 @@
|
|
1
|
-
require "log/logger"
|
2
|
-
include Log
|
3
|
-
|
4
|
-
class Hubeye
|
5
|
-
|
6
|
-
# simple interface to Github's api v3 for commits
|
7
|
-
class Commit
|
8
|
-
attr_reader :raw_input, :repo, :only_sha, :latest
|
9
|
-
def initialize(input)
|
10
|
-
@raw_input = input
|
11
|
-
@repo = input.keys.first
|
12
|
-
if Hash === input
|
13
|
-
if input[@repo].keys == ["sha"]
|
14
|
-
@only_sha = true
|
15
|
-
else
|
16
|
-
@latest = true
|
17
|
-
end
|
18
|
-
else
|
19
|
-
raise ArgumentError.new "input must be a kind of hash"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def sha
|
24
|
-
@sha ||= @raw_input[@repo]['sha']
|
25
|
-
end
|
26
|
-
|
27
|
-
def commit_message
|
28
|
-
if @only_sha
|
29
|
-
return
|
30
|
-
elsif @latest
|
31
|
-
@commit_message ||= @raw_input[@repo]['commit']['message']
|
32
|
-
else
|
33
|
-
raise
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def committer_name
|
38
|
-
if @only_sha
|
39
|
-
return
|
40
|
-
elsif @latest
|
41
|
-
@committer_name ||= @raw_input[@repo]['commit']['committer']['name']
|
42
|
-
else
|
43
|
-
raise
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
module Server
|
49
|
-
attr_accessor :remote_connection
|
50
|
-
attr_reader :socket, :sockets, :session, :daemonized
|
51
|
-
|
52
|
-
# standard lib.
|
53
|
-
require 'socket'
|
54
|
-
require 'yaml'
|
55
|
-
require 'json'
|
56
|
-
require 'open-uri'
|
57
|
-
# hubeye
|
58
|
-
require "config/parser"
|
59
|
-
require "notification/finder"
|
60
|
-
require "hooks/git_hooks"
|
61
|
-
require "hooks/executer"
|
62
|
-
require "helpers/time"
|
63
|
-
include Helpers::Time
|
64
|
-
|
65
|
-
CONFIG_FILE = File.join(ENV['HOME'], ".hubeye", "hubeyerc")
|
66
|
-
CONFIG = {}
|
67
|
-
# find Desktop notification system
|
68
|
-
|
69
|
-
# CONFIG options: defined in ~/.hubeye/hubeyerc
|
70
|
-
#
|
71
|
-
# Option overview:
|
72
|
-
#
|
73
|
-
# CONFIG[:oncearound]: 60 (seconds) is the default amount of time for looking
|
74
|
-
# for changes in every single repository. If tracking lots of repos,
|
75
|
-
# it might be a good idea to increase the value, or hubeye will cry
|
76
|
-
# due to overwork, fatigue and general anhedonia.
|
77
|
-
#
|
78
|
-
# hubeyerc format => oncearound: 1000
|
79
|
-
#
|
80
|
-
# CONFIG[:username] is the username used when not specified.
|
81
|
-
# hubeyerc format => username: 'hansolo'
|
82
|
-
# when set to 'hansolo'
|
83
|
-
# >rails
|
84
|
-
# would track https://www.github.com/hansolo/rails
|
85
|
-
# but a full URI path won't use CONFIG[:username]
|
86
|
-
# >rails/rails
|
87
|
-
# would track https://www.github.com/rails/rails
|
88
|
-
Config::Parser.new(CONFIG_FILE) do |c|
|
89
|
-
CONFIG[:username] = c.username ||
|
90
|
-
`git config --get-regexp github`.split(' ').last
|
91
|
-
CONFIG[:oncearound] = c.oncearound || 60
|
92
|
-
CONFIG[:load_repos] = c.load_repos || []
|
93
|
-
CONFIG[:load_hooks] = c.load_hooks || []
|
94
|
-
CONFIG[:default_track] = c.default_track || nil
|
95
|
-
|
96
|
-
CONFIG[:notification_wanted] = if c.notification_wanted.nil?
|
97
|
-
true
|
98
|
-
else
|
99
|
-
c.notification_wanted
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
CONFIG[:desktop_notification] = Notification::Finder.find_notify if
|
104
|
-
CONFIG[:notification_wanted]
|
105
|
-
|
106
|
-
class Strategy
|
107
|
-
UnknownStrategy = Class.new(StandardError)
|
108
|
-
attr_reader :server, :input
|
109
|
-
|
110
|
-
def initialize(server, options={})
|
111
|
-
@server = server
|
112
|
-
opts = {:internal_input => nil}.merge options
|
113
|
-
if !opts[:internal_input]
|
114
|
-
@input = server.socket.gets
|
115
|
-
# check if the client pressed ^C or ^D
|
116
|
-
if @input.nil?
|
117
|
-
@server.remote_connection = false
|
118
|
-
throw(:invalid_input)
|
119
|
-
end
|
120
|
-
@input.chop!
|
121
|
-
else
|
122
|
-
@input = opts[:internal_input]
|
123
|
-
end
|
124
|
-
unless @input
|
125
|
-
Logger.log "Client on #{@server.socket.peeraddr[2]} disconnected."
|
126
|
-
@server.sockets.delete(socket)
|
127
|
-
@server.socket.close
|
128
|
-
return
|
129
|
-
end
|
130
|
-
@input = @input.strip.downcase
|
131
|
-
@input.gsub! /diiv/, '/'
|
132
|
-
end
|
133
|
-
|
134
|
-
STRATEGY_CLASSES = [ "Shutdown", "Exit", "SaveHook", "SaveRepo",
|
135
|
-
"LoadHook", "LoadRepo", "AddHook", "ListHooks", "ListTracking",
|
136
|
-
"Next", "RmRepo", "AddRepo" ]
|
137
|
-
|
138
|
-
|
139
|
-
STRATEGY_CLASSES.each do |klass_str|
|
140
|
-
binding.eval "class #{klass_str}; end"
|
141
|
-
klass = const_get klass_str.intern
|
142
|
-
klass.class_eval do
|
143
|
-
def initialize matches, strategy, options={}
|
144
|
-
@options = options
|
145
|
-
@matches = matches
|
146
|
-
@server = strategy.server
|
147
|
-
@input = strategy.input
|
148
|
-
@socket = @server.socket
|
149
|
-
@sockets = @server.sockets
|
150
|
-
@session = @server.session
|
151
|
-
call
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
# strategy classes
|
157
|
-
class Exit
|
158
|
-
def call
|
159
|
-
@socket.puts "Bye!"
|
160
|
-
# mark the session as continuous to not wipe the log file
|
161
|
-
@session.continuous = true
|
162
|
-
Logger.log "Closing connection to #{@socket.peeraddr[2]}"
|
163
|
-
@server.remote_connection = false
|
164
|
-
if !@session.tracker.empty?
|
165
|
-
Logger.log "Tracking: #{@session.tracker.keys.join ', '}"
|
166
|
-
end
|
167
|
-
# to look pretty when multiple connections per loop
|
168
|
-
Logger.log ""
|
169
|
-
@sockets.delete(@socket)
|
170
|
-
@socket.close
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
class Shutdown
|
175
|
-
def call
|
176
|
-
Logger.log "Closing connection to #{@socket.peeraddr[2]}"
|
177
|
-
Logger.log "Shutting down... (#{::Hubeye::Server::NOW})"
|
178
|
-
Logger.log ""
|
179
|
-
Logger.log ""
|
180
|
-
@socket.puts("Shutting down server")
|
181
|
-
@sockets.delete(@socket)
|
182
|
-
@socket.close
|
183
|
-
unless @server.daemonized
|
184
|
-
STDOUT.puts "Shutting down gracefully."
|
185
|
-
end
|
186
|
-
exit 0
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
class SaveHook
|
191
|
-
def call
|
192
|
-
hooks = @session.hooks
|
193
|
-
if !hooks.empty?
|
194
|
-
file = "#{ENV['HOME']}/.hubeye/hooks/#{@matches[2]}.yml"
|
195
|
-
if File.exists? file
|
196
|
-
override?
|
197
|
-
end
|
198
|
-
File.open(file, "w") do |f_out|
|
199
|
-
::YAML.dump(hooks, f_out)
|
200
|
-
end
|
201
|
-
@socket.puts("Saved hook#{@matches[1]} as #{@matches[2]}")
|
202
|
-
else
|
203
|
-
@socket.puts("No hook#{@matches[1]} to save")
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
private
|
208
|
-
def override?
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
class SaveRepo
|
213
|
-
def call
|
214
|
-
if !@session.tracker.empty?
|
215
|
-
file = "#{ENV['HOME']}/.hubeye/repos/#{@matches[2]}.yml"
|
216
|
-
if File.exists? file
|
217
|
-
override?
|
218
|
-
end
|
219
|
-
# dump only the repository names, not the shas
|
220
|
-
File.open(file, "w") do |f_out|
|
221
|
-
::YAML.dump(@session.tracker.keys, f_out)
|
222
|
-
end
|
223
|
-
@socket.puts("Saved repo#{@matches[1]} as #{@matches[2]}")
|
224
|
-
else
|
225
|
-
@socket.puts("No remote repos are being tracked")
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
private
|
230
|
-
def override?
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
class LoadHook
|
235
|
-
def call
|
236
|
-
if @options[:internal]
|
237
|
-
@silent = @options[:internal]
|
238
|
-
end
|
239
|
-
hookfile = "#{ENV['HOME']}/.hubeye/hooks/#{@matches[2]}.yml"
|
240
|
-
new_hooks = nil
|
241
|
-
if File.exists?(hookfile)
|
242
|
-
File.open(hookfile) do |f|
|
243
|
-
new_hooks = ::YAML.load(f)
|
244
|
-
end
|
245
|
-
@session.hooks.merge!(new_hooks)
|
246
|
-
unless @silent
|
247
|
-
@socket.puts("Loaded #{@matches[1]} #{@matches[2]}")
|
248
|
-
end
|
249
|
-
else
|
250
|
-
unless @silent
|
251
|
-
@socket.puts("No #{@matches[1]} file to load from")
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
class LoadRepo
|
258
|
-
def call
|
259
|
-
if @options[:internal]
|
260
|
-
@silent = @options[:internal]
|
261
|
-
end
|
262
|
-
if File.exists?(repo_file = "#{ENV['HOME']}/.hubeye/repos/#{@matches[2]}.yml")
|
263
|
-
new_repos = nil
|
264
|
-
File.open(repo_file) do |f|
|
265
|
-
new_repos = ::YAML.load(f)
|
266
|
-
end
|
267
|
-
if !new_repos
|
268
|
-
@socket.puts "Unable to load #{@matches[2]}: empty file" unless @silent
|
269
|
-
return
|
270
|
-
end
|
271
|
-
new_repos.each do |r|
|
272
|
-
# add the repo name to the hubeye tracker
|
273
|
-
commit = @server.track(r)
|
274
|
-
@session.tracker.add_or_replace!(commit.repo, commit.sha)
|
275
|
-
end
|
276
|
-
unless @silent
|
277
|
-
@socket.puts "Loaded #{@matches[2]}.\nTracking:\n#{@session.tracker.keys.join ', '}"
|
278
|
-
end
|
279
|
-
else
|
280
|
-
@socket.puts("No file to load from") unless @silent
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
class AddHook
|
286
|
-
def call
|
287
|
-
cwd = File.expand_path('.')
|
288
|
-
repo = @matches[1]
|
289
|
-
dir = @matches[3]
|
290
|
-
cmd = @matches[4]
|
291
|
-
if repo != nil and cmd != nil
|
292
|
-
if @session.hooks[repo]
|
293
|
-
if dir
|
294
|
-
if @session.hooks[repo][dir]
|
295
|
-
@session.hooks[repo][dir] << cmd
|
296
|
-
else
|
297
|
-
@session.hooks[repo][dir] = [cmd]
|
298
|
-
end
|
299
|
-
else
|
300
|
-
if @session.hooks[repo][cwd]
|
301
|
-
@session.hooks[repo][cwd] << cmd
|
302
|
-
else
|
303
|
-
@session.hooks[repo][cwd] = [cmd]
|
304
|
-
end
|
305
|
-
end
|
306
|
-
else
|
307
|
-
if dir
|
308
|
-
@session.hooks[repo] = {dir => [cmd]}
|
309
|
-
else
|
310
|
-
@session.hooks[repo] = {cwd => [cmd]}
|
311
|
-
end
|
312
|
-
end
|
313
|
-
@socket.puts("Hook added")
|
314
|
-
else
|
315
|
-
@socket.puts("Format: 'hook add user/repo [dir: /my/dir/repo ] cmd: git pull origin'")
|
316
|
-
end
|
317
|
-
end
|
318
|
-
end
|
319
|
-
|
320
|
-
class ListHooks
|
321
|
-
def call
|
322
|
-
unless @session.hooks.empty?
|
323
|
-
pwd = File.expand_path('.')
|
324
|
-
format_string = ""
|
325
|
-
@session.hooks.each do |repo, hash|
|
326
|
-
local_dir = nil
|
327
|
-
command = nil
|
328
|
-
hash.each do |dir,cmd|
|
329
|
-
if dir.nil?
|
330
|
-
local_dir = pwd
|
331
|
-
command = cmd.join("\n" + (' ' * 8))
|
332
|
-
else
|
333
|
-
command = cmd
|
334
|
-
local_dir = dir
|
335
|
-
end
|
336
|
-
end
|
337
|
-
format_string << <<EOS
|
338
|
-
remote: #{repo}
|
339
|
-
dir: #{local_dir}
|
340
|
-
cmds: #{command}\n
|
341
|
-
EOS
|
342
|
-
end
|
343
|
-
@socket.puts(format_string)
|
344
|
-
else
|
345
|
-
@socket.puts("No hooks")
|
346
|
-
end
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
class ListTracking
|
351
|
-
def call
|
352
|
-
output = ''
|
353
|
-
if @options[:details]
|
354
|
-
commit_list = []
|
355
|
-
@session.tracker.keys.each do |repo|
|
356
|
-
commit = @server.track(repo, :list => true)
|
357
|
-
commit_list << commit
|
358
|
-
end
|
359
|
-
commit_list.each do |c|
|
360
|
-
output << c.repo + "\n"
|
361
|
-
underline = '=' * c.repo.length
|
362
|
-
output << underline + "\n"
|
363
|
-
output << c.commit_message + "\n=> " +
|
364
|
-
c.committer_name + "\n"
|
365
|
-
output << "\n" unless c.repo == commit_list.last.repo
|
366
|
-
end
|
367
|
-
else
|
368
|
-
output << @session.tracker.keys.join(', ')
|
369
|
-
end
|
370
|
-
output = "none" if output.empty?
|
371
|
-
@socket.puts(output)
|
372
|
-
end
|
373
|
-
end
|
374
|
-
|
375
|
-
class Next
|
376
|
-
def call
|
377
|
-
@socket.puts("")
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
|
-
class RmRepo
|
382
|
-
def call
|
383
|
-
if @matches[1].include?("/")
|
384
|
-
@session.username, @session.repo_name = @matches[1].split('/')
|
385
|
-
else
|
386
|
-
@session.repo_name = @matches[1]
|
387
|
-
end
|
388
|
-
rm = @session.tracker.delete("#{@session.username}/#{@session.repo_name}")
|
389
|
-
if rm
|
390
|
-
@socket.puts("Stopped watching repository #{@session.username}/#{@session.repo_name}")
|
391
|
-
else
|
392
|
-
@socket.puts("Repository #{@session.username}/#{@session.repo_name} not currently being watched")
|
393
|
-
end
|
394
|
-
end
|
395
|
-
end
|
396
|
-
|
397
|
-
class AddRepo
|
398
|
-
def call
|
399
|
-
if @options and @options[:pwd]
|
400
|
-
@session.repo_name = File.dirname(File.expand_path('.'))
|
401
|
-
elsif @options and @options[:fullpath]
|
402
|
-
@session.username, @session.repo_name = @input.split('/')
|
403
|
-
else
|
404
|
-
@session.repo_name = @input
|
405
|
-
end
|
406
|
-
add_repo
|
407
|
-
end
|
408
|
-
|
409
|
-
private
|
410
|
-
def add_repo
|
411
|
-
full_repo_name = "#{@session.username}/#{@session.repo_name}"
|
412
|
-
commit = @server.track(full_repo_name, :latest => true)
|
413
|
-
new_sha = commit.sha
|
414
|
-
commit_msg = commit.commit_message
|
415
|
-
committer = commit.committer_name
|
416
|
-
msg = "#{commit_msg}\n=> #{committer}"
|
417
|
-
change = @session.tracker.add_or_replace!(full_repo_name, new_sha)
|
418
|
-
# new repo to track
|
419
|
-
if !change
|
420
|
-
@socket.puts("Repository #{full_repo_name} has not changed")
|
421
|
-
return
|
422
|
-
elsif change[:add]
|
423
|
-
# log the fact that the user added a repo to be tracked
|
424
|
-
Logger.log("Added to tracker: #{full_repo_name} (#{::Hubeye::Server::NOW})")
|
425
|
-
# show the user, via the client, the info and commit msg for the commit
|
426
|
-
@socket.puts(msg)
|
427
|
-
elsif change[:replace]
|
428
|
-
change_msg = "New commit on #{full_repo_name}\n"
|
429
|
-
change_msg << msg
|
430
|
-
@socket.puts(change_msg)
|
431
|
-
if @server.daemonized
|
432
|
-
Logger.log_change(full_repo_name, commit_msg, committer)
|
433
|
-
else
|
434
|
-
Logger.log_change(full_repo_name, commit_msg, committer,
|
435
|
-
:include_terminal => true)
|
436
|
-
end
|
437
|
-
end
|
438
|
-
end
|
439
|
-
end
|
440
|
-
|
441
|
-
# STRATEGIES hash
|
442
|
-
# ===============
|
443
|
-
# keys: input matches
|
444
|
-
# OR
|
445
|
-
# lambda {|input| input.something?} => value
|
446
|
-
#
|
447
|
-
# values: lambda {|matchdata, basestrategy| SomeStrategy.new(matchdata, basestrategy)}
|
448
|
-
STRATEGIES = {
|
449
|
-
%r{\Ashutdown\Z} => lambda {|m, s| Shutdown.new(m, s)},
|
450
|
-
%r{\Aquit|exit\Z} => lambda {|m, s| Exit.new(m, s)},
|
451
|
-
%r{\A\s*save hook(s?) as (.+)\Z} => lambda {|m, s| SaveHook.new(m, s)},
|
452
|
-
%r{\A\s*save repo(s?) as (.+)\Z} => lambda {|m, s| SaveRepo.new(m, s)},
|
453
|
-
%r{\A\s*load hook(s?) (.+)\Z} => lambda {|m, s| LoadHook.new(m, s)},
|
454
|
-
%r{\A\s*load repo(s?) (.+)\Z} => lambda {|m, s| LoadRepo.new(m, s)},
|
455
|
-
%r{\A\s*internal load hook(s?) (.+)\Z} => lambda {|m, s| LoadHook.new(m, s, :internal => true)},
|
456
|
-
%r{\A\s*internal load repo(s?) (.+)\Z} => lambda {|m, s| LoadRepo.new(m, s, :internal => true)},
|
457
|
-
%r{\Ahook add ([-\w]+/[-\w]+) (dir:\s?(.*))?\s*cmd:\s?(.*)\Z} => lambda {|m, s| AddHook.new(m, s)},
|
458
|
-
%r{\Ahook list\Z} => lambda {|m, s| ListHooks.new(m, s)},
|
459
|
-
%r{\Atracking\s*\Z} => lambda {|m, s| ListTracking.new(m, s)},
|
460
|
-
%r{\Atracking\s*-d\Z} => lambda {|m, s| ListTracking.new(m, s, :details => true)},
|
461
|
-
%r{^pwd} => lambda {|m, s| AddRepo.new(m, s, :pwd => true)},
|
462
|
-
%r{^\s*$} => lambda {|m, s| Next.new(m, s)},
|
463
|
-
%r{\Arm ([-\w]+/?[-\w]*)\Z} => lambda {|m, s| RmRepo.new(m, s)},
|
464
|
-
lambda {|inp| inp.include? '/'} => lambda {|m, s| AddRepo.new(m, s, :fullpath => true)},
|
465
|
-
lambda {|inp| not inp.nil?} => lambda {|m, s| AddRepo.new(m, s)}
|
466
|
-
}
|
467
|
-
|
468
|
-
def call
|
469
|
-
STRATEGIES.each do |inp,strat|
|
470
|
-
if inp.respond_to? :match
|
471
|
-
if m = @input.match(inp)
|
472
|
-
return strat.call(m, self)
|
473
|
-
end
|
474
|
-
elsif inp.respond_to? :call
|
475
|
-
if m = inp.call(@input)
|
476
|
-
return strat.call(m, self)
|
477
|
-
end
|
478
|
-
end
|
479
|
-
end
|
480
|
-
raise UnknownStrategy
|
481
|
-
end
|
482
|
-
end
|
483
|
-
|
484
|
-
class Session
|
485
|
-
attr_accessor :repo_name, :username, :continuous
|
486
|
-
attr_writer :tracker, :hooks
|
487
|
-
|
488
|
-
def initialize
|
489
|
-
setup_singleton_methods
|
490
|
-
end
|
491
|
-
|
492
|
-
def tracker
|
493
|
-
@tracker ||= {}
|
494
|
-
end
|
495
|
-
|
496
|
-
def hooks
|
497
|
-
@hooks ||= {}
|
498
|
-
end
|
499
|
-
|
500
|
-
def cleanup
|
501
|
-
reset_username
|
502
|
-
reset_repo_name
|
503
|
-
end
|
504
|
-
|
505
|
-
private
|
506
|
-
def reset_username
|
507
|
-
self.username = CONFIG[:username]
|
508
|
-
end
|
509
|
-
|
510
|
-
def reset_repo_name
|
511
|
-
self.repo_name = ""
|
512
|
-
end
|
513
|
-
|
514
|
-
def setup_singleton_methods
|
515
|
-
tracker.singleton_class.class_eval do
|
516
|
-
def add_or_replace! repo_name, new_sha=nil
|
517
|
-
if Hash === repo_name
|
518
|
-
merge! repo_name
|
519
|
-
return true
|
520
|
-
else
|
521
|
-
if keys.include? repo_name and self[repo_name] == new_sha
|
522
|
-
return
|
523
|
-
elsif keys.include? repo_name
|
524
|
-
ret = {:replace => true}
|
525
|
-
else
|
526
|
-
ret = {:add => true}
|
527
|
-
end
|
528
|
-
end
|
529
|
-
merge! repo_name => new_sha
|
530
|
-
ret
|
531
|
-
end
|
532
|
-
end
|
533
|
-
end
|
534
|
-
end # end of Session class
|
535
|
-
|
536
|
-
def start(port, options={})
|
537
|
-
listen(port)
|
538
|
-
setup_env(options)
|
539
|
-
loop do
|
540
|
-
waiting = catch(:connect) do
|
541
|
-
look_for_changes unless @remote_connection
|
542
|
-
end
|
543
|
-
client_connect(@sockets) if waiting
|
544
|
-
catch(:invalid_input) do
|
545
|
-
strategy = Strategy.new(self)
|
546
|
-
strategy.call
|
547
|
-
end
|
548
|
-
@session.cleanup
|
549
|
-
end
|
550
|
-
end
|
551
|
-
|
552
|
-
# The track method closes over a list variable to store recent info on
|
553
|
-
# tracked repositories.
|
554
|
-
# Options: :sha, :latest, :full, :list (all boolean).
|
555
|
-
# The :list => true option gives back the commit object from the list.
|
556
|
-
|
557
|
-
list = []
|
558
|
-
|
559
|
-
define_method :track do |repo, options={}|
|
560
|
-
if repo.include? '/'
|
561
|
-
username, repo_name = repo.split '/'
|
562
|
-
full_repo_name = repo
|
563
|
-
else
|
564
|
-
username, repo_name = @session.username, repo
|
565
|
-
full_repo_name = "#{username}/#{repo_name}"
|
566
|
-
end
|
567
|
-
unless options[:list]
|
568
|
-
hist = nil
|
569
|
-
begin
|
570
|
-
open "https://api.github.com/repos/#{username}/" \
|
571
|
-
"#{repo_name}/commits" do |f|
|
572
|
-
hist = JSON.parse f.read
|
573
|
-
end
|
574
|
-
rescue
|
575
|
-
@socket.puts "Not a Github repository name"
|
576
|
-
throw(:invalid_input)
|
577
|
-
end
|
578
|
-
new_info =
|
579
|
-
{full_repo_name =>
|
580
|
-
{'sha' => hist.first['sha'],
|
581
|
-
'commit' =>
|
582
|
-
{'message' => hist.first['commit']['message'],
|
583
|
-
'committer' => {'name' => hist.first['commit']['committer']['name']}
|
584
|
-
}
|
585
|
-
}
|
586
|
-
}
|
587
|
-
commit = Commit.new(new_info)
|
588
|
-
# update the list
|
589
|
-
list.reject! {|cmt| cmt.repo == full_repo_name}
|
590
|
-
list << commit
|
591
|
-
end
|
592
|
-
if options[:full]
|
593
|
-
# unsupported so far
|
594
|
-
raise ArgumentError.new
|
595
|
-
elsif options[:latest]
|
596
|
-
commit.dup
|
597
|
-
elsif options[:list]
|
598
|
-
list.each {|c| return c if c.repo == full_repo_name}
|
599
|
-
nil
|
600
|
-
else
|
601
|
-
# default
|
602
|
-
Commit.new full_repo_name => {'sha' => hist.first['sha']}
|
603
|
-
end
|
604
|
-
end
|
605
|
-
|
606
|
-
private
|
607
|
-
def listen(port)
|
608
|
-
@server = TCPServer.open(port)
|
609
|
-
end
|
610
|
-
|
611
|
-
def setup_env(options={})
|
612
|
-
@daemonized = options[:daemon]
|
613
|
-
@sockets = [@server] # An array of sockets we'll monitor
|
614
|
-
['INT', 'KILL'].each do |sig|
|
615
|
-
trap(sig) do
|
616
|
-
@sockets.each {|s| s.close}
|
617
|
-
STDOUT.puts
|
618
|
-
exit 1
|
619
|
-
end
|
620
|
-
end
|
621
|
-
@session = Session.new
|
622
|
-
unless CONFIG[:default_track].nil?
|
623
|
-
CONFIG[:default_track].each do |repo|
|
624
|
-
commit = track(repo)
|
625
|
-
@session.tracker.merge! commit.repo => commit.sha
|
626
|
-
end
|
627
|
-
end
|
628
|
-
unless CONFIG[:load_hooks].empty?
|
629
|
-
hooks = CONFIG[:load_hooks].dup
|
630
|
-
session_load :hooks => hooks
|
631
|
-
end
|
632
|
-
unless CONFIG[:load_repos].empty?
|
633
|
-
repos = CONFIG[:load_repos].dup
|
634
|
-
session_load :repos => repos
|
635
|
-
end
|
636
|
-
@session.username = CONFIG[:username]
|
637
|
-
@remote_connection = false
|
638
|
-
end
|
639
|
-
|
640
|
-
def session_load options={}
|
641
|
-
opts = {:hooks => nil, :repos => nil}.merge options
|
642
|
-
if hooks = opts[:hooks]
|
643
|
-
hooks.each do |h|
|
644
|
-
strat = Strategy.new(self, :internal_input => "internal load hook #{h}")
|
645
|
-
strat.call
|
646
|
-
end
|
647
|
-
elsif repos = opts[:repos]
|
648
|
-
repos.each do |r|
|
649
|
-
strat = Strategy.new(self, :internal_input => "internal load repo #{r}")
|
650
|
-
strat.call
|
651
|
-
end
|
652
|
-
else
|
653
|
-
raise ArgumentError.new "Must load either hooks or repos"
|
654
|
-
end
|
655
|
-
end
|
656
|
-
|
657
|
-
#TODO: refactor this ugly, long method into a new class
|
658
|
-
def look_for_changes
|
659
|
-
# if no client is connected, but the commits array contains repos
|
660
|
-
if @sockets.size == 1 and !@session.tracker.empty?
|
661
|
-
|
662
|
-
loop do
|
663
|
-
sleep_amt = CONFIG[:oncearound] / @session.tracker.length
|
664
|
-
@session.tracker.each do |repo,sha|
|
665
|
-
start_time = Time.now
|
666
|
-
commit = track(repo, :latest => true)
|
667
|
-
api_time = (Time.now - start_time).to_i
|
668
|
-
if commit.sha == sha
|
669
|
-
(sleep_amt - api_time).times do
|
670
|
-
sleep 1
|
671
|
-
@remote_connection = client_ready(@sockets) ? true : false
|
672
|
-
throw(:connect, true) if @remote_connection
|
673
|
-
end
|
674
|
-
else
|
675
|
-
# There was a change to a tracked repository.
|
676
|
-
full_repo_name = commit.repo
|
677
|
-
commit_msg = commit.commit_message
|
678
|
-
committer = commit.committer_name
|
679
|
-
new_sha = commit.sha
|
680
|
-
change_msg = "Repo #{full_repo_name} has changed\nNew commit: " \
|
681
|
-
"#{commit_msg}\n=> #{committer}"
|
682
|
-
case CONFIG[:desktop_notification]
|
683
|
-
when "libnotify"
|
684
|
-
Notification::GnomeNotify.notify("Hubeye", change_msg)
|
685
|
-
Logger.log_change(full_repo_name, commit_msg, committer)
|
686
|
-
when "growl"
|
687
|
-
Autotest::Growl.growl("Hubeye", change_msg)
|
688
|
-
Logger.log_change(full_repo_name, commit_msg, committer)
|
689
|
-
when nil
|
690
|
-
if @daemonized
|
691
|
-
Logger.log_change(full_repo_name, commit_msg, committer)
|
692
|
-
else
|
693
|
-
Logger.log_change(full_repo_name, commit_msg, committer, :include_terminal => true)
|
694
|
-
end
|
695
|
-
end
|
696
|
-
# execute any hooks for that repository
|
697
|
-
unless @session.hooks.nil? || @session.hooks.empty?
|
698
|
-
if hooks = @session.hooks[full_repo_name]
|
699
|
-
hooks.each do |dir,cmds|
|
700
|
-
# execute() takes [commands], {options} where
|
701
|
-
# options = :directory and :repo
|
702
|
-
Hooks::Command.execute(cmds, :directory => dir, :repo => full_repo_name)
|
703
|
-
end
|
704
|
-
end
|
705
|
-
end
|
706
|
-
@session.tracker.add_or_replace!(full_repo_name, new_sha)
|
707
|
-
end
|
708
|
-
end
|
709
|
-
end # end of (while remote_connection == false)
|
710
|
-
else
|
711
|
-
@remote_connection = client_ready(@sockets, :block => true) ? true : false
|
712
|
-
throw(:connect, true) if @remote_connection
|
713
|
-
end
|
714
|
-
end
|
715
|
-
|
716
|
-
def client_ready(sockets, options={})
|
717
|
-
if options[:block]
|
718
|
-
select(sockets, nil, nil)
|
719
|
-
else
|
720
|
-
select(sockets, nil, nil, 1)
|
721
|
-
end
|
722
|
-
end
|
723
|
-
|
724
|
-
def client_connect(sockets)
|
725
|
-
ready = select(sockets)
|
726
|
-
readable = ready[0]
|
727
|
-
readable.each do |socket|
|
728
|
-
if socket == @server
|
729
|
-
@socket = @server.accept
|
730
|
-
sockets << @socket
|
731
|
-
# Inform the client of connection
|
732
|
-
basic_inform = "Hubeye running on #{Socket.gethostname} as #{@session.username}"
|
733
|
-
if !@session.tracker.empty?
|
734
|
-
@socket.puts "#{basic_inform}\nTracking: #{@session.tracker.keys.join ', '}"
|
735
|
-
else
|
736
|
-
@socket.puts basic_inform
|
737
|
-
end
|
738
|
-
if !@daemonized
|
739
|
-
puts "Client connected at #{NOW}"
|
740
|
-
end
|
741
|
-
@socket.flush
|
742
|
-
# And log the fact that the client connected
|
743
|
-
# if the client quit, do not wipe the log file
|
744
|
-
if @session.continuous
|
745
|
-
Logger.log "Accepted connection from #{@socket.peeraddr[2]} (#{NOW})"
|
746
|
-
else
|
747
|
-
# wipe the log file and start anew
|
748
|
-
Logger.relog "Accepted connection from #{@socket.peeraddr[2]} (#{NOW})"
|
749
|
-
end
|
750
|
-
Logger.log "local: #{@socket.addr}"
|
751
|
-
Logger.log "peer : #{@socket.peeraddr}"
|
752
|
-
end
|
753
|
-
end
|
754
|
-
end
|
755
|
-
|
756
|
-
end # of Server module
|
757
|
-
|
758
|
-
class HubeyeServer
|
759
|
-
include Server
|
760
|
-
|
761
|
-
def initialize(debug=true)
|
762
|
-
@debug = debug
|
763
|
-
end
|
764
|
-
|
765
|
-
end
|
766
|
-
end
|
767
|
-
|