hubeye 0.0.1
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/LICENSE +20 -0
- data/README.md +74 -0
- data/Rakefile +34 -0
- data/VERSION.rb +5 -0
- data/bin/hubeye +132 -0
- data/lib/client/hubeye_client.rb +90 -0
- data/lib/environment.rb +10 -0
- data/lib/hooks/executer.rb +30 -0
- data/lib/hooks/git_hooks.rb +25 -0
- data/lib/log/logger.rb +39 -0
- data/lib/notification/gnomenotify.rb +11 -0
- data/lib/notification/growl.rb +11 -0
- data/lib/notification/notification.rb +73 -0
- data/lib/server/hubeye_server.rb +634 -0
- data/lib/timehelper/timehelper.rb +3 -0
- data/test/notification.rb +17 -0
- data/test/test.rb +4 -0
- metadata +78 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 by Luke Gruber
|
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 included
|
12
|
+
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 NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#Hubeye
|
2
|
+
|
3
|
+
Hubeye is composed of a client and server. Once the server is run,
|
4
|
+
you can connect to it via the client. Once connected, you'll be
|
5
|
+
prompted by a '>'. Type the name of a Github repository.
|
6
|
+
|
7
|
+
Example: (what the user enters is preceded by the prompt)
|
8
|
+
|
9
|
+
>hubeye
|
10
|
+
commit 77b82b54044c16751228
|
11
|
+
tree 8ce18af1461b5c741003
|
12
|
+
parent ea63fe317fe58dff1c95
|
13
|
+
log tracking info for repos on client quit => luke-gru
|
14
|
+
|
15
|
+
What you see is the latest commit reference, tree reference and parent
|
16
|
+
commit reference on Github for that repository. Note that the user did
|
17
|
+
not type a username. This is because the user defined a username in his
|
18
|
+
~/.hubeyerc file.
|
19
|
+
|
20
|
+
###~/.hubeyerc
|
21
|
+
|
22
|
+
username = luke-gru
|
23
|
+
|
24
|
+
This allows the user to type the repository name only, and to receive
|
25
|
+
information regarding that user's repository. The username must be a
|
26
|
+
valid Github username.
|
27
|
+
|
28
|
+
###Keeping track of repositories
|
29
|
+
|
30
|
+
Hubeye doesn't actually track any repositories unless you disconnect
|
31
|
+
from the server and leave the server running. This can be done by:
|
32
|
+
|
33
|
+
>quit
|
34
|
+
Bye!
|
35
|
+
|
36
|
+
If Hubeye has any repos to watch, it will watch Github for changes.
|
37
|
+
It can keep track of as many repos as you want; just keep typing
|
38
|
+
them in. If Hubeye finds a change in a repo, it will tell you that the
|
39
|
+
repo has changed in the terminal where the server is running. Also, next
|
40
|
+
time you connect to the server via the client, it will remind you.
|
41
|
+
Hubeye does not remove a repo from the watch list unless explicitly
|
42
|
+
told to do so.
|
43
|
+
|
44
|
+
>rm luke-gru/hubeye
|
45
|
+
|
46
|
+
this removes luke-gru's hubeye repo from the watch list. You can also
|
47
|
+
add another user's repo like this:
|
48
|
+
|
49
|
+
>rails/rails
|
50
|
+
|
51
|
+
This adds https://github.com/rails/rails to the watch list.
|
52
|
+
|
53
|
+
###Shutting down
|
54
|
+
|
55
|
+
>shutdown
|
56
|
+
|
57
|
+
Next time you start up the server, the watch list is empty.
|
58
|
+
In order to have a default watch list:
|
59
|
+
|
60
|
+
~/.hubeyerc
|
61
|
+
|
62
|
+
default = rails/rails | dchelimsky/rspec
|
63
|
+
|
64
|
+
These will be watched automatically when starting up the server
|
65
|
+
|
66
|
+
###All ~/.hubeyerc configurations
|
67
|
+
|
68
|
+
username = luke-gru
|
69
|
+
default = username/reponame | username2/reponame2 | myreponame
|
70
|
+
oncearound = 90
|
71
|
+
|
72
|
+
oncearound: number of seconds before completing a check of every repo in
|
73
|
+
the watch list for changes
|
74
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
task :install => :chmod do
|
2
|
+
puts "Done"
|
3
|
+
end
|
4
|
+
|
5
|
+
task :chmod => :makelog do
|
6
|
+
binfile = File.join(File.dirname(__FILE__), "/bin/hubeye")
|
7
|
+
chmod 0777, binfile unless File.executable?(binfile)
|
8
|
+
end
|
9
|
+
|
10
|
+
task :makelog => :config_file do
|
11
|
+
hublog_dir = ENV['HOME'] + "/hublog"
|
12
|
+
mkdir(hublog_dir) unless File.exists?(hublog_dir)
|
13
|
+
|
14
|
+
hooks_dir = hublog_dir + "/hooks"
|
15
|
+
mkdir(hooks_dir) unless File.exists?(hooks_dir)
|
16
|
+
|
17
|
+
repos_dir = hublog_dir + "/repos"
|
18
|
+
mkdir(repos_dir) unless File.exists?(repos_dir)
|
19
|
+
|
20
|
+
hublog_file = File.join(ENV['HOME'], "/hublog/hublog")
|
21
|
+
touch hublog_file unless File.exists?(hublog_file)
|
22
|
+
end
|
23
|
+
|
24
|
+
task :config_file do
|
25
|
+
config_file = File.join(ENV['HOME'], ".hubeyerc")
|
26
|
+
touch config_file unless File.exists? config_file
|
27
|
+
end
|
28
|
+
|
29
|
+
task :config_file => :message
|
30
|
+
|
31
|
+
task :message do
|
32
|
+
puts "Installing Hubeye..."
|
33
|
+
end
|
34
|
+
|
data/VERSION.rb
ADDED
data/bin/hubeye
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# require environment and VERSION files
|
4
|
+
require File.join(File.expand_path(File.dirname(__FILE__) + "/../"), "lib/environment")
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + "/../VERSION")
|
6
|
+
|
7
|
+
# standard lib
|
8
|
+
require 'optparse'
|
9
|
+
require 'ostruct'
|
10
|
+
|
11
|
+
# vendor
|
12
|
+
unless RUBY_VERSION >= '1.9'
|
13
|
+
begin
|
14
|
+
require 'daemons'
|
15
|
+
rescue
|
16
|
+
require 'rubygems'
|
17
|
+
retry
|
18
|
+
else
|
19
|
+
RuntimeError.new "The daemons gem is needed to run hubeye as a daemon " +
|
20
|
+
"on Ruby < 1.9"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Options
|
25
|
+
def self.parse(args)
|
26
|
+
|
27
|
+
# defaults
|
28
|
+
options = OpenStruct.new
|
29
|
+
options.server_wanted = true
|
30
|
+
options.client_wanted = false
|
31
|
+
options.server_daemonized = true
|
32
|
+
options.port = 2000
|
33
|
+
options.host = 'localhost'
|
34
|
+
|
35
|
+
opts_saved = OptionParser.new do |opts|
|
36
|
+
opts.banner = "Usage: hubeye [options]"
|
37
|
+
opts.separator ""
|
38
|
+
opts.separator "Specific options:"
|
39
|
+
opts.separator "Note: The default port (for server and client) is 2000"
|
40
|
+
|
41
|
+
opts.on("-s", "--server", "Start the server (default: daemonized process)") do
|
42
|
+
options.client_wanted = false
|
43
|
+
options.server_wanted = true
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("-t", "--top", "Run server as non-daemonized (output to term)") do
|
47
|
+
options.server_daemonized = false
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on("-o", "--host HOST", "Host that the client connects to. Default: 'localhost'") do |h|
|
51
|
+
options.host = h
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on("-p", "--port PORT", "Port that the server runs on / client connects to") do |p|
|
55
|
+
options.port = p.to_i
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on("-c", "--client", "Start hubeye client to interact with server.") do
|
59
|
+
options.server_wanted = false
|
60
|
+
options.client_wanted = true
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on_tail("-v", "--version", "Show hubeye version") do
|
64
|
+
puts Hubeye::VERSION.join('.')
|
65
|
+
exit
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
69
|
+
puts opts
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
opts_saved.parse!(args)
|
75
|
+
options
|
76
|
+
end # end of Options::parse
|
77
|
+
|
78
|
+
end # end of class
|
79
|
+
|
80
|
+
|
81
|
+
class Hubeye
|
82
|
+
class << self
|
83
|
+
|
84
|
+
def start
|
85
|
+
options = Options.parse(ARGV)
|
86
|
+
host = options.host
|
87
|
+
port = options.port
|
88
|
+
daemonized = options.server_daemonized
|
89
|
+
if options.server_wanted
|
90
|
+
unless port_open?(port)
|
91
|
+
puts "Other instances are running"
|
92
|
+
exit 1
|
93
|
+
end
|
94
|
+
require File.join(Environment::LIBDIR, '/server/hubeye_server.rb')
|
95
|
+
options.server_daemonized ? start_server(port, :daemon => true) : start_server(port, :daemon => false)
|
96
|
+
else
|
97
|
+
require File.join(Environment::LIBDIR, '/client/hubeye_client.rb')
|
98
|
+
start_client(host, port)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
def port_open?(port)
|
104
|
+
# this is the solution until multi-threading is implemented
|
105
|
+
# on the TCP server
|
106
|
+
listening_tcps = `netstat -l --tcp --numeric`
|
107
|
+
if /#{port}/ =~ listening_tcps
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def start_server(port, options={})
|
115
|
+
server = HubeyeServer.new(true) # debug: true
|
116
|
+
if options[:daemon]
|
117
|
+
Process.daemon(true) # don't change dir to '/'
|
118
|
+
end
|
119
|
+
server.start(port, options)
|
120
|
+
end
|
121
|
+
|
122
|
+
# client connection defaults to localhost:2000
|
123
|
+
def start_client(host, port)
|
124
|
+
client = HubeyeClient.new
|
125
|
+
client.start(host, port)
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
Hubeye.start
|
132
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
class HubeyeClient
|
5
|
+
|
6
|
+
def start(host, port)
|
7
|
+
connect(host, port)
|
8
|
+
read_welcome()
|
9
|
+
interact()
|
10
|
+
end
|
11
|
+
|
12
|
+
def _begin
|
13
|
+
begin
|
14
|
+
yield
|
15
|
+
rescue
|
16
|
+
puts $! # Display the exception to the user
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def connect(host, port)
|
21
|
+
_begin do
|
22
|
+
# Give the user some feedback while connecting.
|
23
|
+
STDOUT.print "Connecting..." # Say what we're doing
|
24
|
+
STDOUT.flush # Make it visible right away
|
25
|
+
@s = TCPSocket.open(host, port) # Connect
|
26
|
+
STDOUT.puts "Done" if @s
|
27
|
+
|
28
|
+
# Now display information about the connection.
|
29
|
+
local, peer = @s.addr, @s.peeraddr
|
30
|
+
|
31
|
+
STDOUT.print "Connected to #{peer[2]}:#{peer[1]}"
|
32
|
+
STDOUT.puts " using local port #{local[1]}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_welcome
|
37
|
+
# Wait just a bit, to see if the server sends any initial message.
|
38
|
+
begin
|
39
|
+
sleep(0.5) # Wait half a second
|
40
|
+
msg = @s.readpartial(4096) # Read whatever is ready
|
41
|
+
STDOUT.puts msg.chop # And display it
|
42
|
+
# If nothing was ready to read, just ignore the exception.
|
43
|
+
rescue SystemCallError
|
44
|
+
rescue NoMethodError
|
45
|
+
STDOUT.puts "The server's not running!"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Now begin a loop of client/server interaction.
|
50
|
+
def interact
|
51
|
+
while @s
|
52
|
+
loop do
|
53
|
+
STDOUT.print '> ' # Display prompt for local input
|
54
|
+
STDOUT.flush # Make sure the prompt is visible
|
55
|
+
local = STDIN.gets # Read line from the console
|
56
|
+
#break if !local # Quit if no input from console
|
57
|
+
if local.match(/^\.$/) #pwd
|
58
|
+
@s.puts(local.gsub(/\A\.\Z/, "pwd" + Dir.pwd.split('/').last)) # Send the line to the server, daemons gem strips some special chars (/, :)
|
59
|
+
else
|
60
|
+
@s.puts(local.gsub(/\//, 'diiv'))
|
61
|
+
end
|
62
|
+
@s.flush # Force it out
|
63
|
+
# Read the server's response and print out.
|
64
|
+
# The server may send more than one line, so use readpartial
|
65
|
+
# to read whatever it sends (as long as it all arrives in one chunk).
|
66
|
+
|
67
|
+
sleep 0.5
|
68
|
+
begin
|
69
|
+
response = @s.readpartial(4096)
|
70
|
+
rescue EOFError
|
71
|
+
response = "Bye!"
|
72
|
+
end
|
73
|
+
|
74
|
+
if response.chop.strip == "Bye!"
|
75
|
+
puts(response.chop)
|
76
|
+
@s.close
|
77
|
+
exit
|
78
|
+
elsif response.chop.strip.match(/shutting/i)
|
79
|
+
@s.close
|
80
|
+
exit
|
81
|
+
else
|
82
|
+
puts(response.chop) # Display response to user
|
83
|
+
next
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end #end of class
|
90
|
+
|
data/lib/environment.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Hooks
|
2
|
+
class Command
|
3
|
+
|
4
|
+
class NoHookError < ArgumentError; end
|
5
|
+
|
6
|
+
#options include the directory to execute the command
|
7
|
+
#(that's it for now, will add more functionality later)
|
8
|
+
def self.execute(commands=[], options={})
|
9
|
+
opts = {:directory => nil, :repo => nil}.merge(options)
|
10
|
+
dir = opts[:directory]
|
11
|
+
repo = opts[:repo]
|
12
|
+
begin
|
13
|
+
|
14
|
+
commands.each do |cmd|
|
15
|
+
if dir
|
16
|
+
Dir.chdir(File.expand_path(dir)) do
|
17
|
+
::Kernel.system cmd
|
18
|
+
end
|
19
|
+
else
|
20
|
+
::Kernel.system cmd
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
rescue ArgumentError
|
25
|
+
raise NoHookError.new "There aren't any hook commands for the repository #{repo}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Hooks
|
2
|
+
module Git
|
3
|
+
|
4
|
+
module Default
|
5
|
+
|
6
|
+
def self.fetch(local_reponame, remotename, branchname)
|
7
|
+
#first, start a process to cd to the local repo
|
8
|
+
#then fetch the remote
|
9
|
+
Dir.chdir(File.expand_path(local_reponame)) do
|
10
|
+
::Kernel.system("git fetch #{remotename} #{branchname}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.pull(local_reponame, remotename, branchname)
|
15
|
+
#first, start a process to cd to the local repo
|
16
|
+
#then pull the remote
|
17
|
+
Dir.chdir(File.expand_path(local_reponame)) do
|
18
|
+
::Kernel.system("git pull #{remotename} #{branchname}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/log/logger.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
class Logger
|
2
|
+
|
3
|
+
def self.log(msg)
|
4
|
+
File.open(ENV['HOME'] + "/hublog" * 2, "a") do |f|
|
5
|
+
f.puts(msg)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.relog(msg)
|
10
|
+
#wipe the file and start anew
|
11
|
+
File.open(ENV['HOME'] + "/hublog" * 2, "w") do |f|
|
12
|
+
f.puts(msg)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
##If {include_socket: true} is passed, then log to the client as well. If
|
17
|
+
#{include_terminal: true}, log to the terminal too (make sure that the
|
18
|
+
#process is not daemonized). Always log to the logfile.
|
19
|
+
|
20
|
+
def self.log_change(repo_name, commit_msg, committer, options={})
|
21
|
+
opts = {:include_socket => false, :include_terminal => false}.merge options
|
22
|
+
change_msg = <<-MSG
|
23
|
+
===============================
|
24
|
+
Repository: #{repo_name.downcase.strip} has changed (#{Time.now.strftime("%m/%d/%Y at %I:%M%p")})
|
25
|
+
Commit msg: #{commit_msg}
|
26
|
+
Committer : #{committer}
|
27
|
+
===============================
|
28
|
+
MSG
|
29
|
+
if opts[:include_socket]
|
30
|
+
socket.puts(change_msg)
|
31
|
+
end
|
32
|
+
|
33
|
+
if opts[:include_terminal]
|
34
|
+
puts change_msg
|
35
|
+
end
|
36
|
+
Logger.log(change_msg)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Autotest::GnomeNotify
|
2
|
+
|
3
|
+
EXPIRATION_IN_SECONDS = 2
|
4
|
+
CHANGE_ICON = File.join(File.expand_path("images", ::Environment::ROOTDIR), "change_icon.jpg")
|
5
|
+
|
6
|
+
def self.notify(title, msg, img=CHANGE_ICON)
|
7
|
+
options = "-t #{EXPIRATION_IN_SECONDS * 1000} -i #{img}"
|
8
|
+
system "notify-send #{options} '#{title}' '#{msg}'"
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Autotest::Growl
|
2
|
+
|
3
|
+
EXPIRATION_IN_SECONDS = 2
|
4
|
+
dir = File.dirname(__FILE__)
|
5
|
+
CHANGE_ICON = File.expand_path(dir + "/../images/change_icon.jpg")
|
6
|
+
|
7
|
+
def self.growl(title, msg, img=CHANGE_ICON, pri=0, stick="")
|
8
|
+
system "growlnotify -n autotest --image #{img} -p #{pri} -m #{msg.inspect} #{title} #{stick}"
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Notification
|
2
|
+
|
3
|
+
class Finder
|
4
|
+
|
5
|
+
def self.find_notify
|
6
|
+
if RUBY_PLATFORM =~ /mswin/
|
7
|
+
return
|
8
|
+
elsif RUBY_PLATFORM =~ /linux/
|
9
|
+
libnotify = system('locate libnotify-bin > /dev/null')
|
10
|
+
|
11
|
+
if libnotify && LibChecker.autotest
|
12
|
+
require_relative "gnomenotify"
|
13
|
+
return "libnotify"
|
14
|
+
elsif LibChecker.autotest_notification
|
15
|
+
require_relative "growl"
|
16
|
+
return "growl"
|
17
|
+
else
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
21
|
+
elsif RUBY_PLATFORM =~ /darwin/i
|
22
|
+
|
23
|
+
if LibChecker.autotest_notification
|
24
|
+
require_relative "growl"
|
25
|
+
return "growl"
|
26
|
+
else
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class LibChecker
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def autotest
|
39
|
+
begin
|
40
|
+
require 'autotest'
|
41
|
+
rescue LoadError
|
42
|
+
if require 'rubygems'
|
43
|
+
retry
|
44
|
+
else
|
45
|
+
return
|
46
|
+
end
|
47
|
+
end
|
48
|
+
if defined? Autotest
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def autotest_notification
|
55
|
+
begin
|
56
|
+
require 'autotest_notification'
|
57
|
+
rescue LoadError
|
58
|
+
if require 'rubygems'
|
59
|
+
retry
|
60
|
+
else
|
61
|
+
return
|
62
|
+
end
|
63
|
+
end
|
64
|
+
if defined? Autotest
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end # end module
|
73
|
+
|
@@ -0,0 +1,634 @@
|
|
1
|
+
module Server
|
2
|
+
# standard lib.
|
3
|
+
require 'socket'
|
4
|
+
require 'open-uri'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
# vendor
|
8
|
+
begin
|
9
|
+
require 'nokogiri'
|
10
|
+
rescue LoadError
|
11
|
+
if require 'rubygems'
|
12
|
+
retry
|
13
|
+
else
|
14
|
+
abort 'Nokogiri is needed to run hubeye. Gem install nokogiri'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# hubeye
|
19
|
+
require "notification/notification"
|
20
|
+
require "log/logger"
|
21
|
+
require "timehelper/timehelper"
|
22
|
+
require "hooks/git_hooks"
|
23
|
+
require "hooks/executer"
|
24
|
+
|
25
|
+
# ONCEAROUND: 30 (seconds) is the default amount of time for looking
|
26
|
+
# for changes in every single repository. If tracking lots of repos,
|
27
|
+
# it might be a good idea to increase the value, or hubeye will cry
|
28
|
+
# due to overwork, fatigue and general anhedonia.
|
29
|
+
ONCEAROUND = 30
|
30
|
+
# USERNAME: defined in ~/.hubeyerc
|
31
|
+
USERNAME = 'luke-gru'
|
32
|
+
# find Desktop notification system
|
33
|
+
DESKTOP_NOTIFICATION = Notification::Finder.find_notify
|
34
|
+
|
35
|
+
class InputError < StandardError; end
|
36
|
+
|
37
|
+
def start(port, options={})
|
38
|
+
listen(port)
|
39
|
+
setup_env(options)
|
40
|
+
loop do
|
41
|
+
catch(:next) do
|
42
|
+
not_connected() unless @remote_connection
|
43
|
+
get_input(@socket)
|
44
|
+
puts @input if @debug
|
45
|
+
parse_input()
|
46
|
+
get_github_doc("/#{@username}/#{@repo_name}")
|
47
|
+
parse_doc()
|
48
|
+
@username = USERNAME
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Listen on port (2000 is the default)
|
54
|
+
def listen(port)
|
55
|
+
@server = TCPServer.open(port)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def setup_env(options={})
|
60
|
+
@daemonized = options[:daemon]
|
61
|
+
@sockets = [@server] # An array of sockets we'll monitor
|
62
|
+
@ary_commits_repos = []
|
63
|
+
@hubeye_tracker = []
|
64
|
+
# @username changes if input includes a '/' when removing, adding tracked
|
65
|
+
# repos.
|
66
|
+
@username = 'luke-gru'
|
67
|
+
@remote_connection = false
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def not_connected
|
72
|
+
# if no client is connected, but the commits array contains repos
|
73
|
+
if @sockets.size == 1 and @ary_commits_repos.empty? == false
|
74
|
+
|
75
|
+
while @remote_connection == false
|
76
|
+
@hubeye_tracker.each do |repo|
|
77
|
+
doc = Nokogiri::HTML(open("https://github.com/#{repo}/"))
|
78
|
+
|
79
|
+
# make variable not block-local
|
80
|
+
commit_msg = nil
|
81
|
+
|
82
|
+
doc.xpath('//div[@class = "message"]/pre').each do |node|
|
83
|
+
commit_msg = node.text
|
84
|
+
if @ary_commits_repos.include?(commit_msg)
|
85
|
+
ONCEAROUND.times do
|
86
|
+
sleep 1
|
87
|
+
@remote_connection = client_ready(@sockets) ? true : false
|
88
|
+
break if @remote_connection
|
89
|
+
end
|
90
|
+
else
|
91
|
+
# There was a change to a tracked repository.
|
92
|
+
|
93
|
+
# make variable not block-local
|
94
|
+
committer = nil
|
95
|
+
|
96
|
+
doc.xpath('//div[@class = "actor"]/div[@class = "name"]').each do |node|
|
97
|
+
committer = node.text
|
98
|
+
end
|
99
|
+
|
100
|
+
# notify of change to repository
|
101
|
+
# if they have a Desktop notification
|
102
|
+
# library installed
|
103
|
+
change_msg = "Repo #{repo} has changed\nNew commit: #{commit_msg} => #{committer}"
|
104
|
+
case DESKTOP_NOTIFICATION
|
105
|
+
when "libnotify"
|
106
|
+
Autotest::GnomeNotify.notify("Hubeye", change_msg)
|
107
|
+
Logger.log_change(repo, commit_msg, committer)
|
108
|
+
when "growl"
|
109
|
+
Autotest::Growl.growl("Hubeye", change_msg)
|
110
|
+
Logger.log_change(repo, commit_msg, committer)
|
111
|
+
when nil
|
112
|
+
|
113
|
+
if @daemonized
|
114
|
+
Logger.log_change(repo, commit_msg, committer)
|
115
|
+
else
|
116
|
+
Logger.log_change(repo, commit_msg, committer, :include_terminal => true)
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
# execute any hooks for that repository
|
121
|
+
unless @hook_cmds.nil? || @hook_cmds.empty?
|
122
|
+
if @hook_cmds[repo]
|
123
|
+
hook_cmds = @hook_cmds[repo].dup
|
124
|
+
dir = (hook_cmds.include?('/') ? hook_cmds.shift : nil)
|
125
|
+
|
126
|
+
# execute() takes [commands], {options} where
|
127
|
+
# options = :directory and :repo
|
128
|
+
Hooks::Command.execute(hook_cmds, :directory => dir, :repo => repo)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
@ary_commits_repos << repo
|
133
|
+
@ary_commits_repos << commit_msg
|
134
|
+
# delete the repo and old commit that appear first in the array
|
135
|
+
index_old_HEAD = @ary_commits_repos.index(repo)
|
136
|
+
@ary_commits_repos.delete_at(index_old_HEAD)
|
137
|
+
# and again to get rid of the commit message
|
138
|
+
@ary_commits_repos.delete_at(index_old_HEAD)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
redo unless @remote_connection
|
143
|
+
end # end of (while remote_connection == false)
|
144
|
+
end
|
145
|
+
client_connect(@sockets)
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def client_ready(sockets)
|
150
|
+
select(sockets, nil, nil, 2)
|
151
|
+
end
|
152
|
+
private :client_ready
|
153
|
+
|
154
|
+
|
155
|
+
def client_connect(sockets)
|
156
|
+
ready = select(sockets)
|
157
|
+
readable = ready[0] # These sockets are readable
|
158
|
+
readable.each do |socket| # Loop through readable sockets
|
159
|
+
if socket == @server # If the server socket is ready
|
160
|
+
client = @server.accept # Accept a new client
|
161
|
+
@socket = client # From here on in referred to as @socket
|
162
|
+
sockets << @socket # Add it to the set of sockets
|
163
|
+
# Inform the client of connection
|
164
|
+
if !@hubeye_tracker.empty?
|
165
|
+
@socket.puts "Hubeye running on #{Socket.gethostname}\nTracking:#{@hubeye_tracker.join(' ')}"
|
166
|
+
else
|
167
|
+
@socket.puts "Hubeye running on #{Socket.gethostname}"
|
168
|
+
end
|
169
|
+
|
170
|
+
if !@daemonized
|
171
|
+
puts "Client connected at #{::TimeHelper::NOW}"
|
172
|
+
end
|
173
|
+
|
174
|
+
@socket.flush
|
175
|
+
# And log the fact that the client connected
|
176
|
+
if @still_logging == true
|
177
|
+
# if the client quit, do not wipe the log file
|
178
|
+
Logger.log "Accepted connection from #{@socket.peeraddr[2]} (#{::TimeHelper::NOW})"
|
179
|
+
else
|
180
|
+
# wipe the log file and start anew
|
181
|
+
Logger.relog "Accepted connection from #{@socket.peeraddr[2]} (#{::TimeHelper::NOW})"
|
182
|
+
end
|
183
|
+
Logger.log "local: #{@socket.addr}"
|
184
|
+
Logger.log "peer : #{@socket.peeraddr}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
def get_input(socket)
|
191
|
+
@input = socket.gets # Read input from the client
|
192
|
+
@input.chop! unless @input.nil? # Trim client's input
|
193
|
+
# If no input, the client has disconnected
|
194
|
+
if !@input
|
195
|
+
Logger.log "Client on #{socket.peeraddr[2]} disconnected."
|
196
|
+
@sockets.delete(socket) # Stop monitoring this socket
|
197
|
+
socket.close # Close it
|
198
|
+
throw(:next) # And go on to the next
|
199
|
+
end
|
200
|
+
end
|
201
|
+
private :get_input
|
202
|
+
|
203
|
+
|
204
|
+
def parse_input
|
205
|
+
@input.strip!; @input.downcase!
|
206
|
+
if parse_quit()
|
207
|
+
elsif parse_shutdown()
|
208
|
+
elsif save_hooks_or_repos()
|
209
|
+
elsif load_hooks_or_repos()
|
210
|
+
# parse_hook must be before parse_fullpath_add for the moment
|
211
|
+
elsif parse_hook()
|
212
|
+
elsif hook_list()
|
213
|
+
elsif tracking_list()
|
214
|
+
elsif parse_pwd()
|
215
|
+
elsif parse_empty()
|
216
|
+
elsif parse_remove()
|
217
|
+
elsif parse_fullpath_add()
|
218
|
+
elsif parse_add()
|
219
|
+
else
|
220
|
+
raise InputError "Invalid input #{@input}"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
def parse_quit
|
226
|
+
if @input =~ /\Aquit|exit\Z/ # If the client asks to quit
|
227
|
+
@socket.puts("Bye!") # Say goodbye
|
228
|
+
Logger.log "Closing connection to #{@socket.peeraddr[2]}"
|
229
|
+
@remote_connection = false
|
230
|
+
if !@ary_commits_repos.empty?
|
231
|
+
Logger.log "Tracking: "
|
232
|
+
@ary_commits_repos.each do |repo|
|
233
|
+
Logger.log repo if @ary_commits_repos.index(repo).even?
|
234
|
+
end
|
235
|
+
end
|
236
|
+
Logger.log "" # to look pretty when multiple connections per loop
|
237
|
+
@sockets.delete(@socket) # Stop monitoring the socket
|
238
|
+
@socket.close # Terminate the session
|
239
|
+
# still_logging makes the server not wipe the log file
|
240
|
+
@still_logging = true
|
241
|
+
else
|
242
|
+
return
|
243
|
+
end
|
244
|
+
throw(:next)
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
def parse_shutdown
|
249
|
+
if @input == "shutdown"
|
250
|
+
# local
|
251
|
+
Logger.log "Closing connection to #{@socket.peeraddr[2]}"
|
252
|
+
Logger.log "Shutting down... (#{::TimeHelper::NOW})"
|
253
|
+
Logger.log ""
|
254
|
+
Logger.log ""
|
255
|
+
# peer
|
256
|
+
@socket.puts("Shutting down server")
|
257
|
+
else
|
258
|
+
return
|
259
|
+
end
|
260
|
+
shutdown()
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
def shutdown
|
265
|
+
@sockets.delete(@socket)
|
266
|
+
@socket.close
|
267
|
+
exit
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
def parse_hook
|
272
|
+
if %r{githook add} =~ @input
|
273
|
+
githook_add()
|
274
|
+
else
|
275
|
+
return
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
##
|
281
|
+
# @hook_cmds:
|
282
|
+
# repo is the key, value is array of directory and commands. First element
|
283
|
+
# of array is the local directory for that remote repo, rest are commands
|
284
|
+
# related to hooks called on change of commit message (with plans to change
|
285
|
+
# that to commit SHA reference) of the remote repo
|
286
|
+
def githook_add
|
287
|
+
@input.gsub!(/diiv/, '/')
|
288
|
+
# make match-$globals parse input
|
289
|
+
@input =~ /add ([^\/]+\/\w+) (dir: (\S*) )?cmd: (.*)\Z/
|
290
|
+
@hook_cmds ||= {}
|
291
|
+
if $1 != nil && $4 != nil
|
292
|
+
if @hook_cmds[$1]
|
293
|
+
@hook_cmds[$1] << $4
|
294
|
+
elsif $2 != nil
|
295
|
+
@hook_cmds[$1] = [$3, $4]
|
296
|
+
else
|
297
|
+
@hook_cmds[$1] = [$4]
|
298
|
+
end
|
299
|
+
@socket.puts("Hook added")
|
300
|
+
else
|
301
|
+
@socket.puts("Format: 'githook add user/repo [dir: /my/dir/repo ] cmd: git pull origin'")
|
302
|
+
end
|
303
|
+
throw(:next)
|
304
|
+
end
|
305
|
+
private :githook_add
|
306
|
+
|
307
|
+
|
308
|
+
def save_hooks_or_repos
|
309
|
+
if @input =~ %r{\A\s*save hook(s?) as (.+)\Z}
|
310
|
+
if !@hook_cmds.nil? && !@hook_cmds.empty?
|
311
|
+
File.open("#{ENV['HOME']}/hublog/hooks/#{$2}.yml", "w") do |f_out|
|
312
|
+
::YAML.dump(@hook_cmds, f_out)
|
313
|
+
end
|
314
|
+
@socket.puts("Saved hook#{$1} as #{$2}")
|
315
|
+
else
|
316
|
+
@socket.puts("No hook#{$1} to save")
|
317
|
+
end
|
318
|
+
throw(:next)
|
319
|
+
elsif @input =~ %r{\A\s*save repo(s?) as (.+)\Z}
|
320
|
+
if !@hubeye_tracker.empty?
|
321
|
+
File.open("#{ENV['HOME']}/hublog/repos/#{$2}.yml", "w") do |f_out|
|
322
|
+
::YAML.dump(@hubeye_tracker, f_out)
|
323
|
+
end
|
324
|
+
@socket.puts("Saved repo#{$1} as #{$2}")
|
325
|
+
else
|
326
|
+
@socket.puts("No remote repos are being tracked")
|
327
|
+
end
|
328
|
+
throw(:next)
|
329
|
+
else
|
330
|
+
return
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
|
335
|
+
def load_hooks_or_repos
|
336
|
+
if @input =~ %r{\A\s*load hook(s?) (.+)\Z}
|
337
|
+
hookfile = "#{ENV['HOME']}/hublog/hooks/#{$2}.yml"
|
338
|
+
|
339
|
+
# establish non block-local scope
|
340
|
+
newhooks = nil
|
341
|
+
|
342
|
+
if File.exists?(hookfile)
|
343
|
+
File.open(hookfile) do |f|
|
344
|
+
newhooks = ::YAML.load(f)
|
345
|
+
end
|
346
|
+
@hook_cmds ||= {}
|
347
|
+
@hook_cmds = newhooks.merge(@hook_cmds)
|
348
|
+
@socket.puts("Loaded #{$1} #{$2}")
|
349
|
+
else
|
350
|
+
@socket.puts("No #{$1} file to load from")
|
351
|
+
end
|
352
|
+
throw(:next)
|
353
|
+
elsif @input =~ %r{\A\s*load repo(s)? (.+)\Z}
|
354
|
+
if File.exists? repo_file = "#{ENV['HOME']}/hublog/repos/#{$2}.yml"
|
355
|
+
newrepos = nil
|
356
|
+
File.open(repo_file) do |f|
|
357
|
+
newrepos = ::YAML.load(f)
|
358
|
+
end
|
359
|
+
|
360
|
+
if !newrepos
|
361
|
+
@socket.puts "Unable to load #{$2}: empty file"
|
362
|
+
throw(:next)
|
363
|
+
end
|
364
|
+
# newrepos is an array of repos to be tracked
|
365
|
+
newrepos.each do |e|
|
366
|
+
# append the repo and the newest commit message to the repos and
|
367
|
+
# commit messages array, then inform the client of the newest
|
368
|
+
# commit message
|
369
|
+
commit_msg = get_commit_msg(e)
|
370
|
+
# if the commit_msg is non-false, don't do anything, otherwise 'next'
|
371
|
+
commit_msg ? nil : next
|
372
|
+
@ary_commits_repos << e << commit_msg
|
373
|
+
# and append the repo to the hubeye_tracker array
|
374
|
+
@hubeye_tracker << e
|
375
|
+
end
|
376
|
+
@ary_commits_repos.uniq!
|
377
|
+
@hubeye_tracker.uniq!
|
378
|
+
|
379
|
+
@socket.puts "Loaded #{$2}.\nTracking:\n#{show_repos_pretty()}"
|
380
|
+
else
|
381
|
+
# no repo file with that name
|
382
|
+
@socket.puts("No file to load from")
|
383
|
+
end
|
384
|
+
throw(:next)
|
385
|
+
end
|
386
|
+
return
|
387
|
+
end
|
388
|
+
|
389
|
+
|
390
|
+
# helper method to get commit message for a
|
391
|
+
# single repo
|
392
|
+
def get_commit_msg(remote_repo)
|
393
|
+
begin
|
394
|
+
@doc = Nokogiri::HTML(open("https://github.com#{'/' + remote_repo}"))
|
395
|
+
rescue
|
396
|
+
return nil
|
397
|
+
end
|
398
|
+
# returns the commit message
|
399
|
+
commit_msg = parse_msg(@doc)
|
400
|
+
end
|
401
|
+
|
402
|
+
|
403
|
+
def show_repos_pretty
|
404
|
+
pretty_repos = ""
|
405
|
+
@ary_commits_repos.each do |e|
|
406
|
+
if @ary_commits_repos.index(e).even?
|
407
|
+
pretty_repos += e + "\n"
|
408
|
+
else
|
409
|
+
pretty_repos += " " + e + "\n"
|
410
|
+
end
|
411
|
+
end
|
412
|
+
pretty_repos
|
413
|
+
end
|
414
|
+
|
415
|
+
|
416
|
+
def hook_list
|
417
|
+
if @input =~ %r{hook list}
|
418
|
+
unless @hook_cmds.nil? || @hook_cmds.empty?
|
419
|
+
@hook_cmds.each do |repo, ary|
|
420
|
+
remote = repo
|
421
|
+
if ary.first.include? '/'
|
422
|
+
local = ary.first
|
423
|
+
cmds = ary[1..-1]
|
424
|
+
else
|
425
|
+
cmds = ary
|
426
|
+
local = "N/A"
|
427
|
+
end
|
428
|
+
format_string = <<-EOS
|
429
|
+
remote: #{remote}
|
430
|
+
dir : #{local}
|
431
|
+
cmds: #{cmds.each {|cmd| print cmd + ' ' }} \n
|
432
|
+
EOS
|
433
|
+
end
|
434
|
+
@socket.puts(format_string)
|
435
|
+
@socketspoke = true
|
436
|
+
end
|
437
|
+
else
|
438
|
+
return
|
439
|
+
end
|
440
|
+
@socket.puts("No hooks") unless @socketspoke
|
441
|
+
@socketspoke = nil
|
442
|
+
throw(:next)
|
443
|
+
end
|
444
|
+
|
445
|
+
|
446
|
+
# show the client what repos (with commit messages)
|
447
|
+
# they're tracking
|
448
|
+
def tracking_list
|
449
|
+
if @input =~ /\Atracking\s*\Z/
|
450
|
+
list = show_repos_pretty
|
451
|
+
@socket.puts(list)
|
452
|
+
throw(:next)
|
453
|
+
else
|
454
|
+
return
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
|
459
|
+
# This means the user pressed '.' in the client,
|
460
|
+
# wanting to track the pwd repo. The period is replaced
|
461
|
+
# by 'pwd' in the client application and sent to @input because of
|
462
|
+
# problems with the period getting stripped in TCP transit. The name
|
463
|
+
# of the client's present working directory comes right after the 'pwd'.
|
464
|
+
# Typing '.' in the client only works (ie: begins tracking the remote repo)
|
465
|
+
# if the root directory of the git repository has the same name as one of
|
466
|
+
# the user's github repositories.
|
467
|
+
def parse_pwd
|
468
|
+
if @input.match(/^pwd/)
|
469
|
+
@repo_name = @input[3..-1]
|
470
|
+
else
|
471
|
+
return
|
472
|
+
end
|
473
|
+
return true
|
474
|
+
end
|
475
|
+
|
476
|
+
|
477
|
+
def parse_empty
|
478
|
+
if @input == ''
|
479
|
+
@socket.puts("")
|
480
|
+
throw(:next)
|
481
|
+
else
|
482
|
+
return
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
|
487
|
+
# Like the method parse_pwd, in which the client application replaces
|
488
|
+
# the '.' with 'pwd' and sends that to this server instead, parse_remove
|
489
|
+
# does pretty much the same thing with '/'. This is because of the slash
|
490
|
+
# getting stripped in TCP transit. Here, the slash is replaced with 'diiv',
|
491
|
+
# as this is unlikely to be included anywhere in a real username/repository
|
492
|
+
# combination (or is it... duh duh DUUUUHH)
|
493
|
+
# p.s. no it isn't
|
494
|
+
def parse_remove
|
495
|
+
if %r{\Arm ([\w-](diiv)?[\w-]*)\Z} =~ @input
|
496
|
+
if $1.include?("diiv")
|
497
|
+
@username, @repo_name = $1.split('diiv')
|
498
|
+
else
|
499
|
+
@username, @repo_name = "#{@username}/#{$1}".split('/')
|
500
|
+
end
|
501
|
+
|
502
|
+
begin
|
503
|
+
index_found = @ary_commits_repos.index("#{@username}/#{@repo_name}")
|
504
|
+
if index_found
|
505
|
+
# consecutive indices in the array
|
506
|
+
for i in 1..2
|
507
|
+
@ary_commits_repos.delete_at(index_found)
|
508
|
+
end
|
509
|
+
@hubeye_tracker.delete("#{@username}/#{@repo_name}")
|
510
|
+
@socket.puts("Stopped watching repository #{@username}/#{@repo_name}")
|
511
|
+
sleep 0.5
|
512
|
+
throw(:next)
|
513
|
+
else
|
514
|
+
@socket.puts("Repository #{@username}/#{@repo_name} not currently being watched")
|
515
|
+
throw(:next)
|
516
|
+
end
|
517
|
+
rescue
|
518
|
+
@socket.puts($!)
|
519
|
+
throw(:next)
|
520
|
+
end
|
521
|
+
else
|
522
|
+
return
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
|
527
|
+
def parse_fullpath_add
|
528
|
+
if @input.include?('diiv')
|
529
|
+
# includes a '/', such as rails/rails, but in the adding to tracker context
|
530
|
+
@username, @repo_name = @input.split('diiv')
|
531
|
+
else
|
532
|
+
return
|
533
|
+
end
|
534
|
+
return true
|
535
|
+
end
|
536
|
+
|
537
|
+
|
538
|
+
# if the input is not the above special scenarios
|
539
|
+
def parse_add
|
540
|
+
@repo_name = @input
|
541
|
+
end
|
542
|
+
|
543
|
+
|
544
|
+
def get_github_doc(full_repo_path)
|
545
|
+
begin
|
546
|
+
# if adding a repo with another username
|
547
|
+
@doc = Nokogiri::HTML(open("https://github.com#{full_repo_path}"))
|
548
|
+
rescue OpenURI::HTTPError
|
549
|
+
@socket.puts("Not a Github repository!")
|
550
|
+
throw(:next)
|
551
|
+
rescue URI::InvalidURIError
|
552
|
+
@socket.puts("Not a valid URI")
|
553
|
+
throw(:next)
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
|
558
|
+
def parse_doc
|
559
|
+
# get commit msg
|
560
|
+
@commit_msg = parse_msg(@doc)
|
561
|
+
# get committer
|
562
|
+
@committer = parse_committer()
|
563
|
+
|
564
|
+
# new repo to track
|
565
|
+
full_repo_name = "#{@username}/#{@repo_name}"
|
566
|
+
if !@ary_commits_repos.include?(full_repo_name)
|
567
|
+
@ary_commits_repos << full_repo_name
|
568
|
+
@hubeye_tracker << full_repo_name
|
569
|
+
@ary_commits_repos << @commit_msg
|
570
|
+
# get commit info
|
571
|
+
@info = parse_info()
|
572
|
+
@msg = "#{@commit_msg} => #{@committer}".gsub(/\(author\)/, '')
|
573
|
+
# log the fact that the user added a repo to be tracked
|
574
|
+
Logger.log("Added to tracker: #{@ary_commits_repos[-2]} (#{::TimeHelper::NOW})")
|
575
|
+
# show the user, via the client, the info and commit msg for the commit
|
576
|
+
@socket.puts("#{@info}\n#{@msg}")
|
577
|
+
|
578
|
+
# new commit to tracked repo
|
579
|
+
elsif !@ary_commits_repos.include?(@commit_msg)
|
580
|
+
begin
|
581
|
+
index_of_msg = @ary_commits_repos.index(@username + "/" + @repo_name) + 1
|
582
|
+
@ary_commits_repos.delete_at(index_of_msg)
|
583
|
+
@ary_commits_repos.insert(index_of_msg - 1, @commit_msg)
|
584
|
+
|
585
|
+
# log to the logfile and tell the client
|
586
|
+
if @daemonized
|
587
|
+
Logger.log_change(@repo_name, @commit_msg, @committer,
|
588
|
+
:include_socket => true)
|
589
|
+
else
|
590
|
+
Logger.log_change(@repo_name, @commit_msg, @committer,
|
591
|
+
:include_socket => true, :include_terminal => true)
|
592
|
+
end
|
593
|
+
rescue
|
594
|
+
@socket.puts($!)
|
595
|
+
end
|
596
|
+
else
|
597
|
+
# no change to the tracked repo
|
598
|
+
@socket.puts("Repository #{@repo_name} has not changed")
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
|
603
|
+
def parse_msg(html_doc)
|
604
|
+
# get commit msg
|
605
|
+
html_doc.xpath('//div[@class = "message"]/pre').each do |node|
|
606
|
+
return commit_msg = node.text
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
|
611
|
+
def parse_committer
|
612
|
+
@doc.xpath('//div[@class = "actor"]/div[@class = "name"]').each do |node|
|
613
|
+
return committer = node.text
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
|
618
|
+
def parse_info
|
619
|
+
@doc.xpath('//div[@class = "machine"]').each do |node|
|
620
|
+
return info = node.text.gsub(/\n/, '').gsub(/tree/, "\ntree").gsub(/parent.*?(\w)/, "\nparent \\1").strip!
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
end # of Server module
|
625
|
+
|
626
|
+
class HubeyeServer
|
627
|
+
include Server
|
628
|
+
|
629
|
+
def initialize(debug=false)
|
630
|
+
@debug = debug
|
631
|
+
end
|
632
|
+
|
633
|
+
end
|
634
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "/../lib/notification/notification")
|
2
|
+
|
3
|
+
class NotifyTests < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_libnotify_on_linux
|
6
|
+
if RUBY_PLATFORM =~ /linux/i
|
7
|
+
assert_equal "libnotify", ::Notification::Finder.find_notify
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_growl_returns_on_darwin
|
12
|
+
if RUBY_PLATFORM =~ /darwin/i
|
13
|
+
assert_equal "growl", ::Notification::Finder.find_notify
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/test/test.rb
ADDED
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hubeye
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Luke Gruber
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-08-31 00:00:00.000000000 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: nokogiri
|
17
|
+
requirement: &69555570 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *69555570
|
26
|
+
description: Github repository commit watcher -- keep your eye on new commits from
|
27
|
+
multiple repos through an interactive CLI
|
28
|
+
email: luke.gru@gmail.com
|
29
|
+
executables:
|
30
|
+
- hubeye
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/client/hubeye_client.rb
|
35
|
+
- lib/notification/growl.rb
|
36
|
+
- lib/notification/gnomenotify.rb
|
37
|
+
- lib/notification/notification.rb
|
38
|
+
- lib/timehelper/timehelper.rb
|
39
|
+
- lib/server/hubeye_server.rb
|
40
|
+
- lib/hooks/executer.rb
|
41
|
+
- lib/hooks/git_hooks.rb
|
42
|
+
- lib/log/logger.rb
|
43
|
+
- lib/environment.rb
|
44
|
+
- bin/hubeye
|
45
|
+
- Rakefile
|
46
|
+
- README.md
|
47
|
+
- VERSION.rb
|
48
|
+
- LICENSE
|
49
|
+
- test/test.rb
|
50
|
+
- test/notification.rb
|
51
|
+
has_rdoc: true
|
52
|
+
homepage:
|
53
|
+
licenses:
|
54
|
+
- MIT
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 1.8.7
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.6.2
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Github repository commit watcher -- keep your eye on new commits from multiple
|
77
|
+
repos through an interactive CLI
|
78
|
+
test_files: []
|