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