hubeye 0.3.0 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
|