hubeye 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|