├── .gitignore ├── Gemfile ├── README.md ├── Rakefile ├── bin └── githubwatcher ├── githubwatcher.gemspec ├── images └── icon.png └── lib ├── githubwatcher.rb └── githubwatcher └── version.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | .rvmrc 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in githubwatcher.gemspec 4 | gemspec -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Github Watcher 2 | 3 | Github watcher is a simple and useful gem that help you to constantly monitor your repositories to get updates when: 4 | 5 | * Number of watchers change 6 | * Number of forks change 7 | * Number of issues change 8 | * Repo was updated 9 | 10 | It uses [foreverb](https://github.com/DAddYE/foreverb) to demonize the process. 11 | 12 | A [demonstration video is here](http://www.daddye.it/post/7275490125/github-growl) 13 | 14 | ## Prerequisites 15 | 16 | You need to have **growlnotify** installed. To do that you can install it through official [site](http://growl.info) or 17 | if you have the awesome [brew](https://github.com/mxcl/homebrew) simply with: 18 | 19 | ``` sh 20 | $ brew install growlnotify 21 | ``` 22 | 23 | ## Installation 24 | 25 | ``` sh 26 | $ sudo gem install githubwatcher 27 | $ githubwatcher start 28 | $ githubwatcher stop 29 | ``` 30 | 31 | ## Configuration 32 | 33 | You need to tell to our program which repositories you want to watch, to do that simply edit ```~/.githubwatcher/repos.yaml``` 34 | with your favorite editor. 35 | 36 | Should look like this (if you have ran ```githubwatcher start```) 37 | 38 | We provide a `config` command to easily edit it. 39 | 40 | ``` sh 41 | $ githubwatcher config 42 | ``` 43 | 44 | It will open in texmate or vim this: 45 | 46 | ``` yaml 47 | --- 48 | - daddye/all 49 | - padrino/all 50 | ``` 51 | 52 | So if for example you want to watch [sinatra](https://github.com/sinatra/sinatra) add it, the result should look like: 53 | 54 | ``` yaml 55 | --- 56 | - daddye/all 57 | - padrino/all 58 | - sinatra/sinatra 59 | ``` 60 | 61 | If you want to watch **all** repositories of a given user you simply provide **/all** so will look like: 62 | 63 | ``` yaml 64 | --- 65 | - daddye/all 66 | - padrino/all 67 | - sinatra/all 68 | ``` 69 | 70 | Restart the deamon 71 | 72 | ``` sh 73 | $ githubwatcher restart 74 | ``` 75 | 76 | ## Using a different API 77 | 78 | Simply edit `~/.githubwatcher/api.yaml` and set a custom url and api version. If you are using GitHub:FI the 79 | version has to be v2 for now. 80 | 81 | ## Working with Ruby Forever 82 | 83 | ``` sh 84 | $ foreverb list 85 | PID RSS CPU CMD 86 | 12494 27132 0.2 Forever: /usr/bin/githubwatcher 87 | 88 | $ foreverb stop github 89 | Do you want really stop Forever: /usr/bin/githubwatcher with pid 12494? y 90 | Killing process Forever: /usr/bin/githubwatcher with pid 12494... 91 | ``` 92 | 93 | Your are done! 94 | 95 | ## Hacks 96 | 97 | In some env you use `sudo gem install`, so in this case the first time you launch the app use `sudo`, 98 | in this way will be generated the `Gemfile.lock`, in future you will be able to run it without `sudo`. 99 | 100 | ## Author 101 | 102 | DAddYE, you can follow me on twitter [@daddye](http://twitter.com/daddye) 103 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' unless defined?(Gem) 2 | require 'bundler/gem_tasks' 3 | 4 | %w(install release).each do |task| 5 | Rake::Task[task].enhance do 6 | sh "rm -rf pkg" 7 | end 8 | end 9 | 10 | desc 'Bump version on github' 11 | task :bump do 12 | if `git status -s`.strip == '' 13 | puts "\e[31mNothing to commit (working directory clean)\e[0m" 14 | else 15 | version = Bundler.load_gemspec(Dir[File.expand_path('../*.gemspec', __FILE__)].first).version 16 | sh "git add .; git commit -a -m \"Bump to version #{version}\"" 17 | end 18 | end 19 | 20 | task :release => :bump 21 | -------------------------------------------------------------------------------- /bin/githubwatcher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | $:.unshift File.expand_path('../../lib', __FILE__) 3 | require 'rubygems' unless defined?(Gem) 4 | require 'forever' 5 | require 'githubwatcher' 6 | 7 | case arg = ARGV[0] 8 | when "configure" 9 | editor = `which mate`.chomp! || `which vim`.chomp! 10 | puts "Im unable to find an editor, open manually ~/.githubwatcher/repos.yaml" and exit unless editor 11 | system editor, File.expand_path('~/.githubwatcher/repos.yaml') and exit 12 | when "reset" 13 | system "rm -rf ~/.githubwatcher" 14 | end 15 | 16 | Forever.run do 17 | dir File.expand_path('~/.githubwatcher') 18 | 19 | on_error do |e| 20 | unless @_errors.include?(e.message) 21 | @_errors << e.message 22 | Githubwatcher.notify("Error!", e.message) 23 | end 24 | end 25 | 26 | before :all do 27 | @_errors = [] # Store common errors 28 | Githubwatcher.setup 29 | Githubwatcher.notify("GitHub Watcher", "was started...") 30 | end 31 | 32 | every 5.seconds do 33 | Githubwatcher.run 34 | end 35 | 36 | after :all do 37 | Githubwatcher.notify("GitHub Watcher", "was stopped...") 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /githubwatcher.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "githubwatcher/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "githubwatcher" 7 | s.version = Githubwatcher::VERSION 8 | s.authors = ["Davide D'Agostino"] 9 | s.email = ["d.dagostino@lipsiasoft.com"] 10 | s.homepage = "https://github.com/DAddYE/githubwatcher" 11 | s.summary = "Github Growl Watcher, watch any project and receive growl notification" 12 | s.description = "Github Growl Watcher, watch any project and receive growl notification for updates, new watchers, forks and issues" 13 | 14 | s.rubyforge_project = "githubwatcher" 15 | 16 | s.files = Dir["**/*"].reject { |f| File.directory?(f) || f == "Gemfile.lock" } 17 | s.test_files = [] 18 | s.executables = %w[githubwatcher] 19 | s.require_paths = %w[lib] 20 | s.add_dependency 'yajl-ruby', '~>1.1.0' 21 | s.add_dependency 'httparty', '~>0.8.1' 22 | s.add_dependency 'notifier', '~>0.1.4' 23 | s.add_dependency 'foreverb', '~>0.3.0' 24 | end 25 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DAddYE/githubwatcher/1c0277c1866693f8a221f61e6142f3c407539096/images/icon.png -------------------------------------------------------------------------------- /lib/githubwatcher.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' unless defined?(YAML) 2 | require 'githubwatcher/version' 3 | require 'httparty' 4 | require 'notifier' 5 | 6 | YAML::ENGINE.yamler = "syck" if defined?(YAML::ENGINE) 7 | 8 | module Githubwatcher 9 | extend self 10 | include HTTParty 11 | include Notifier 12 | 13 | API = File.expand_path("~/.githubwatcher/api.yaml") 14 | WATCH = File.expand_path("~/.githubwatcher/repos.yaml") 15 | DB = File.expand_path("~/.githubwatcher/db.yaml") 16 | 17 | format :json 18 | 19 | def setup 20 | puts "Starting GitHub Watcher..." 21 | 22 | unless File.exist?(API) 23 | Dir.mkdir(File.dirname(API)) unless File.exist?(File.dirname(WATCH)) 24 | File.open(API, "w") { |f| f.write ["https://api.github.com", "v3"].to_yaml } 25 | end 26 | 27 | unless File.exist?(WATCH) 28 | warn "Add the repositories you're willing to monitor editing: ~/.githubwatcher/repos.yaml" 29 | Dir.mkdir(File.dirname(WATCH)) unless File.exist?(File.dirname(WATCH)) 30 | File.open(WATCH, "w") { |f| f.write ["daddye/all", "padrino/all"].to_yaml } 31 | end 32 | 33 | @_watch = YAML.load_file(WATCH) 34 | @base_uri, @api_version = YAML.load_file(API) 35 | @_repos = YAML.load_file(DB) if File.exist?(DB) 36 | 37 | base_uri @base_uri 38 | end 39 | 40 | def start! 41 | repos_was = repos.dup 42 | watch.each do |value| 43 | key, value = *value.split("/") 44 | r = get_repositories(key) 45 | r.each do |repo| 46 | next unless value.include?(repo["name"]) || value.include?("all") 47 | puts "Quering #{repo["git_url"]}..." 48 | 49 | found = repos_was.find { |r| r["name"] == repo["name"] } 50 | 51 | repo_fullname = [repo['owner']['login'],repo['name']].join('/') 52 | if !found 53 | notify(repo_fullname, "Was created") 54 | repos_was << repo 55 | repos << repo 56 | end 57 | 58 | repo_was = repos_was.find { |r| r["name"] == repo["name"] } 59 | 60 | if repo_was["watchers"] != repo["watchers"] 61 | notify(repo_fullname, "Has new #{repo["watchers"]-repo_was["watchers"]} watchers") 62 | repo_was["watchers"] = repo["watchers"] 63 | end 64 | 65 | if repo_was["open_issues"] != repo["open_issues"] 66 | notify(repo_fullname, "Has new #{repo["open_issues"]-repo_was["open_issues"]} open issues") 67 | repo_was["open_issues"] = repo["open_issues"] 68 | end 69 | 70 | if repo_was["pushed_at"] != repo["pushed_at"] 71 | notify(repo_fullname, "Was updated!") 72 | repo_was["pushed_at"] = repo["pushed_at"] 73 | end 74 | 75 | if repo_was["forks"] != repo["forks"] 76 | notify(repo_fullname, "Has new #{repo["forks"]-repo_was["forks"]} forks") 77 | repo_was["forks"] = repo["forks"] 78 | end 79 | 80 | found = repo if found 81 | end 82 | end 83 | Dir.mkdir(File.dirname(DB)) unless File.exist?(File.dirname(DB)) 84 | File.open(DB, "w"){ |f| f.write @_repos.to_yaml } 85 | end 86 | alias :run :start! 87 | 88 | def repos 89 | @_repos ||= [] 90 | end 91 | 92 | def watch 93 | @_watch ||= [] 94 | end 95 | 96 | def notify(title, text, sticky=false) 97 | Growl.notify(:message => text, :title => title, :image => File.expand_path("../../images/icon.png", __FILE__)); sleep 0.2 98 | puts "=> #{title}: #{text}" 99 | end 100 | 101 | def get_repositories(key) 102 | r = get resource_url(key) 103 | @api_version == "v3" ? r : r["repositories"] 104 | end 105 | 106 | def resource_url(key) 107 | case @api_version 108 | when "v3" 109 | "/users/%s/repos" % key 110 | when "v2" 111 | "/api/v2/json/repos/show/%s" % key 112 | else 113 | raise "Unkown api version" 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /lib/githubwatcher/version.rb: -------------------------------------------------------------------------------- 1 | module Githubwatcher 2 | VERSION = "0.1.1" 3 | end 4 | --------------------------------------------------------------------------------