diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d25313d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config/previously.txt \ No newline at end of file diff --git a/README.rdoc b/README.rdoc deleted file mode 100644 index 17a609a..0000000 --- a/README.rdoc +++ /dev/null @@ -1,44 +0,0 @@ -== Git Commit Notifier - - by Csoma Zoltan (Primalgrasp) (zoltan 'at' primalgrasp 'dot' com) - -Sends email commit messages splitting commits that were pushed in one -step. Email is delivered as text or HTML with changes refined per -word. Emails have a scanable subject containing the first sentence of -the commit as well as the author, project and branch name. - -For example: - - [rails][master] Fix Brasilia timezone. [#1180 state:resolved] - -A reply-to header is added containing the author of the commit. This -makes follow up really simple. If multiple commits are pushed at once, -emails are numbered in chronological order: - [rails][master][000] Added deprecated warning messages to Float#months and Float#years deprications. - [rails][master][001] Enhance testing for fractional days and weeks. Update changelog. - -== Requirements - -- Ruby -- RubyGems -- diff/lcs gem -- SMTP server or sendmail compatible mailer -- mocha, hpricot gems for testing - -== Installing and Configuring - -Make sure the following Git settings are correct: -- git config hooks.mailinglist (email address of the recipient, probably your mailing list address) -- git config hooks.emailprefix (application name, used in email subject) -- see /usr/local/share/git_commit_notifier/config/config.yml for overriding these settings - -Run the automated installation script: - sudo rake install - -To update already installed script, use: - sudo rake update - -See /usr/local/share/git_commit_notifier/config/config.yml for setting your SMTP server address and some other mail options. - -== License -MIT License, see the file LICENSE. diff --git a/Rakefile b/Rakefile index f28fa79..c584fe1 100644 --- a/Rakefile +++ b/Rakefile @@ -10,7 +10,7 @@ desc "Run tests" task :test do |test| Rake::TestTask.new do |t| t.libs << "test" - t.test_files = FileList['git_commit_notifier/test/*.rb'] + t.test_files = FileList['test/*.rb'] t.verbose = true end end @@ -24,15 +24,6 @@ task :install do |install| raise 'hooks directory not found for the specified project - cannot continue' unless File.exist?(hooks_dir) hooks_dir += '/' unless hooks_dir[-1,-1] == '/' - # load default config file - config = YAML::load_file('git_commit_notifier/config/config.yml') - - config.merge!({'projects' => - { project_path => - { 'application_name' => '', 'recipient_address' => ''} - } - }) - install_path = '/usr/local/share' install_script_files(install_path) @@ -40,12 +31,6 @@ task :install do |install| execute_cmd "cp post-receive #{hooks_dir}" execute_cmd "chmod a+x #{hooks_dir}post-receive" - # write config file - config_file = "#{install_path}/git_commit_notifier/config/config.yml" - File.open(config_file, 'w') do |f| - YAML.dump(config, f) - end - Dir.chdir(project_path) puts "Warning: no Git mailing list setting exists for your project. Please go to your project directory and set it with the git config hooks.mailinglist=you@yourdomain.com command or specify 'recipient_address' in the #{config_file} file else no emails can be sent out.\n\n" if `git config hooks.mailinglist`.empty? diff --git a/post-receive b/bin/post-receive similarity index 58% rename from post-receive rename to bin/post-receive index ac5533b..692ac35 100755 --- a/post-receive +++ b/bin/post-receive @@ -1,6 +1,8 @@ #!/usr/bin/env ruby # parameters: revision1, revision 2, branch -require '/usr/local/share/git_commit_notifier/lib/commit_hook' +THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__ +$:.unshift File.join(File.dirname(THIS_FILE), "../lib") +require "commit_hook" if ARGV[0].nil? param = STDIN.gets.strip.split diff --git a/git_commit_notifier/config/config.yml b/config/email.yml.sample similarity index 72% rename from git_commit_notifier/config/config.yml rename to config/email.yml.sample index 96a2ccd..5af9d03 100644 --- a/git_commit_notifier/config/config.yml +++ b/config/email.yml.sample @@ -1,7 +1,4 @@ -projects: - -email: - delivery_method: sendmail # smtp or sendmail +delivery_method: sendmail # smtp or sendmail smtp_server: address: localhost diff --git a/git_commit_notifier/lib/commit_hook.rb b/git_commit_notifier/lib/commit_hook.rb deleted file mode 100644 index 88097ff..0000000 --- a/git_commit_notifier/lib/commit_hook.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'rubygems' -require 'cgi' -require 'net/smtp' -require 'sha1' -require File.dirname(__FILE__) + '/diff_to_html' -require File.dirname(__FILE__) + '/emailer' -require File.dirname(__FILE__) + '/git' - -class CommitHook - - def self.run(rev1, rev2, ref_name, config_file = nil) - - config = YAML.load_file(config_file || '/usr/local/share/git_commit_notifier/config/config.yml') - project_path = Dir.getwd - - project_config = config['projects'] && config['projects'][project_path] ? config['projects'][project_path] : nil - - recipient = project_config ? project_config['recipient_address'] : '' - recipient = Git.mailing_list_address if recipient.empty? - - repo = project_config ? project_config['application_name'] : '' - repo = Git.prefix if repo.empty? - repo = 'scm' if repo.empty? - prefix = "[#{repo}][#{short_ref_name(ref_name)}]" - - diff2html = DiffToHtml.new - diff2html.diff_between_revisions rev1, rev2, repo, ref_name - unless recipient.empty? - diff2html.result.reverse.each_with_index do |result, i| - nr = number(diff2html.result.size, i) - emailer = Emailer.new config, project_path, recipient, result[:commit_info][:email], result[:commit_info][:author], - "#{prefix}#{nr} #{result[:commit_info][:message]}", result[:text_content], result[:html_content], rev1, rev2, ref_name - emailer.send - end - end - end - - def self.number(total_entries, i) - return '' if total_entries <= 1 - digits = total_entries < 10 ? 1 : 3 - '[' + sprintf("%0#{digits}d", i) + ']' - end - - def self.short_ref_name(ref_name) - ref_name.strip.split('/').last - end - -end diff --git a/git_commit_notifier/lib/git.rb b/git_commit_notifier/lib/git.rb deleted file mode 100644 index ced727c..0000000 --- a/git_commit_notifier/lib/git.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Git - def self.show(rev) - `git show #{rev.strip} -w` - end - - def self.log(rev1, rev2) - `git log #{rev1}..#{rev2}`.strip - end - - def self.prefix - `git config hooks.emailprefix`.strip - end - - def self.mailing_list_address - `git config hooks.mailinglist`.strip - end -end diff --git a/lib/commit_hook.rb b/lib/commit_hook.rb new file mode 100644 index 0000000..1587f06 --- /dev/null +++ b/lib/commit_hook.rb @@ -0,0 +1,34 @@ +require 'rubygems' +require 'cgi' +require 'net/smtp' +require 'sha1' + +require 'diff_to_html' +require 'emailer' +require 'git' + +class CommitHook + + def self.run(rev1, rev2, ref_name) + project_path = Dir.getwd + recipient = Git.mailing_list_address + prefix = Git.repo_name + branch_name = (ref_name =~ /master$/i) ? "" : "/#{ref_name.split("/").last}" + + diff2html = DiffToHtml.new + diff2html.diff_between_revisions rev1, rev2, prefix, ref_name + diff2html.result.reverse.each_with_index do |result, i| + nr = number(diff2html.result.size, i) + emailer = Emailer.new project_path, recipient, result[:commit_info][:email], result[:commit_info][:author], + "[#{prefix}#{branch_name}]#{nr} #{result[:commit_info][:message]}", result[:text_content], result[:html_content], rev1, rev2, ref_name + emailer.send + end + end + + def self.number(total_entries, i) + return '' if total_entries <= 1 + digits = total_entries < 10 ? 1 : 3 + '[' + sprintf("%0#{digits}d", i) + ']' + end + +end diff --git a/git_commit_notifier/lib/diff_to_html.rb b/lib/diff_to_html.rb similarity index 90% rename from git_commit_notifier/lib/diff_to_html.rb rename to lib/diff_to_html.rb index 1b2615d..8558388 100644 --- a/git_commit_notifier/lib/diff_to_html.rb +++ b/lib/diff_to_html.rb @@ -239,7 +239,7 @@ def author_name_and_email(info) end def first_sentence(message_array) - msg = message_array.join("\n").split(/(\.\s)|\n/).first.to_s.strip + msg = message_array.first.to_s.strip return message_array.first if msg.empty? || msg =~ /^Merge\:/ msg end @@ -247,14 +247,31 @@ def first_sentence(message_array) def diff_between_revisions(rev1, rev2, repo, branch) @result = [] if rev1 == rev2 - commits = [[rev1]] + commits = [rev1] + elsif rev1 =~ /^0+$/ + # creating a new remote branch + commits = Git.branch_commits(branch) + elsif rev2 =~ /^0+$/ + # deleting an existing remote branch + commits = [] else log = Git.log(rev1, rev2) - commits = log.scan /commit\s([a-f0-9]+)/ + commits = log.scan(/^commit\s([a-f0-9]+)/).map{|match| match[0]} end + if defined?(Test::Unit) + previous_list = [] + else + previous_file = (defined?(THIS_FILE) && THIS_FILE) ? File.join(File.dirname(THIS_FILE), "../config/previously.txt") : "/tmp/previously.txt" + previous_list = (File.read(previous_file).to_a.map {|sha| sha.chomp!} if File.exist?(previous_file)) || [] + end + + commits.reject!{|c| c.find{|sha| previous_list.include?(sha)} } + current_list = (previous_list + commits.flatten).last(1000) + File.open(previous_file, "w"){|f| f << current_list.join("\n") } unless current_list.empty? || defined?(Test::Unit) + commits.each_with_index do |commit, i| - raw_diff = Git.show(commit[0]) + raw_diff = Git.show(commit) raise "git show output is empty" if raw_diff.empty? @last_raw = raw_diff diff --git a/git_commit_notifier/lib/emailer.rb b/lib/emailer.rb similarity index 64% rename from git_commit_notifier/lib/emailer.rb rename to lib/emailer.rb index 0fff8b4..2913ec0 100644 --- a/git_commit_notifier/lib/emailer.rb +++ b/lib/emailer.rb @@ -1,19 +1,24 @@ require 'yaml' +require 'erb' class Emailer - def initialize(config, project_path, recipient, from_address, from_alias, subject, text_message, html_message, old_rev, new_rev, ref_name) - @config = config + def initialize(project_path, recipient, from_address, from_alias, subject, text_message, html_diff, old_rev, new_rev, ref_name) + config_file = File.join(File.dirname(THIS_FILE), '../config/email.yml') + @config = YAML::load_file(config_file) if File.exist?(config_file) + @config ||= {} @project_path = project_path @recipient = recipient @from_address = from_address @from_alias = from_alias @subject = subject @text_message = text_message - @html_message = format_html(html_message) @ref_name = ref_name @old_rev = old_rev @new_rev = new_rev + + template = File.join(File.dirname(__FILE__), '/../template/email.html.erb') + @html_message = ERB.new(File.read(template)).result(binding) end def boundary @@ -23,28 +28,12 @@ def boundary @boundary = Digest::SHA1.hexdigest(seed) end - def format_html(html_diff) -< - - - -#{html_diff} - - -EOF - end - - def read_css - out = '' - File.open(File.dirname(__FILE__) + '/../stylesheets/styles.css').each { |line| - out += line - } - out + def stylesheet_string + stylesheet = File.join(File.dirname(__FILE__), '/../template/styles.css') + File.read(stylesheet) end - def perform_delivery_smtp(content,smtp_settings) + def perform_delivery_smtp(content, smtp_settings) settings = { } %w(address port domain user_name password authentication).each do |key| val = smtp_settings[key].to_s.empty? ? nil : smtp_settings[key] @@ -60,13 +49,14 @@ def perform_delivery_smtp(content,smtp_settings) end end - def perform_delivery_sendmail(content, sendmail_settings) - args = '-i -t ' - args += sendmail_settings['arguments'].to_s - IO.popen("#{sendmail_settings['location']} #{args}","w+") do |f| - content.each do |line| - f.puts line - end + def perform_delivery_sendmail(content, options = nil) + sendmail_settings = { + 'location' => "/usr/sbin/sendmail", + 'arguments' => "-i -t" + }.merge(options || {}) + command = "#{sendmail_settings['location']} #{sendmail_settings['arguments']}" + IO.popen(command, "w+") do |f| + f.write(content.join("\n")) f.flush end end @@ -77,6 +67,7 @@ def send "Reply-To: #{from}", "To: #{@recipient}", "Subject: #{@subject}", + "X-Mailer: git-commit-notifier", "X-Git-Refname: #{@ref_name}", "X-Git-Oldrev: #{@old_rev}", "X-Git-Newrev: #{@new_rev}", @@ -93,10 +84,17 @@ def send "Content-Disposition: inline\n\n\n", @html_message, "--#{boundary}--"] - if @config['email']['delivery_method'] == 'smtp' + + if @recipient.empty? + puts content.join("\n") + return + end + + if @config['delivery_method'] == 'smtp' perform_delivery_smtp(content, @config['smtp_server']) else perform_delivery_sendmail(content, @config['sendmail_options']) end end + end diff --git a/lib/git.rb b/lib/git.rb new file mode 100644 index 0000000..e315412 --- /dev/null +++ b/lib/git.rb @@ -0,0 +1,35 @@ +class Git + def self.show(rev) + `git show #{rev.strip} -w` + end + + def self.log(rev1, rev2) + `git log #{rev1}..#{rev2}`.strip + end + + def self.branch_commits(treeish) + args = Git.branch_heads - [Git.branch_head(treeish)] + args.map! {|tree| "^#{tree}"} + args << treeish + `git rev-list #{args.join(' ')}`.to_a.map{|commit| commit.chomp} + end + + def self.branch_heads + `git rev-parse --branches`.to_a.map{|head| head.chomp} + end + + def self.branch_head(treeish) + `git rev-parse #{treeish}`.strip + end + + def self.repo_name + git_prefix = `git config hooks.emailprefix`.strip + return git_prefix unless git_prefix.empty? + dir_name = `pwd`.chomp.split("/").last.gsub(/\.git$/, '') + return "#{dir_name}" + end + + def self.mailing_list_address + `git config hooks.mailinglist`.strip + end +end diff --git a/git_commit_notifier/lib/result_processor.rb b/lib/result_processor.rb similarity index 100% rename from git_commit_notifier/lib/result_processor.rb rename to lib/result_processor.rb diff --git a/template/email.html.erb b/template/email.html.erb new file mode 100644 index 0000000..7798796 --- /dev/null +++ b/template/email.html.erb @@ -0,0 +1,9 @@ + + + + + <%= html_diff %> + + diff --git a/git_commit_notifier/stylesheets/styles.css b/template/styles.css similarity index 96% rename from git_commit_notifier/stylesheets/styles.css rename to template/styles.css index 390a4e3..bc60c4b 100644 --- a/git_commit_notifier/stylesheets/styles.css +++ b/template/styles.css @@ -1,4 +1,4 @@ -* {font-size:12px;} +* {font-size:11px;} h2 {font-family:Verdana;font-size:12px;background-color: #bbb; font-weight: bold; line-height:25px; margin-bottom:2px; padding-left:5px} table {width:100%;border-collapse:collapse} .r {background-color: #fdd;} diff --git a/git_commit_notifier/test/commit_hook_test.rb b/test/commit_hook_test.rb similarity index 89% rename from git_commit_notifier/test/commit_hook_test.rb rename to test/commit_hook_test.rb index de7fb9d..be712f1 100644 --- a/git_commit_notifier/test/commit_hook_test.rb +++ b/test/commit_hook_test.rb @@ -17,7 +17,6 @@ def test_hook emailer = mock('Emailer') Emailer.expects(:new).times(4).returns(emailer) # 4 commit, one email for each of them emailer.expects(:send).times(4) - config_file = File.dirname(__FILE__) + '/../config/config.yml' - CommitHook.run REVISIONS.first, REVISIONS.last, 'refs/heads/master', config_file + CommitHook.run REVISIONS.first, REVISIONS.last, 'refs/heads/master' end end diff --git a/git_commit_notifier/test/diff_to_html_test.rb b/test/diff_to_html_test.rb similarity index 97% rename from git_commit_notifier/test/diff_to_html_test.rb rename to test/diff_to_html_test.rb index d2140ba..9a9acbe 100644 --- a/git_commit_notifier/test/diff_to_html_test.rb +++ b/test/diff_to_html_test.rb @@ -68,7 +68,7 @@ def test_single_commit diff = DiffToHtml.new diff.diff_between_revisions REVISIONS.first, REVISIONS.first, 'testproject', 'master' assert_equal 1, diff.result.size # single result for a single commit - assert_equal 'Allow use of :path_prefix and :name_prefix outside of namespaced routes', diff.result.first[:commit_info][:message] + assert_equal 'Allow use of :path_prefix and :name_prefix outside of namespaced routes. [#1188 state:resolved]', diff.result.first[:commit_info][:message] assert_equal 'Tom Stuart', diff.result.first[:commit_info][:author] assert_equal 'tom@experthuman.com', diff.result.first[:commit_info][:email] diff --git a/git_commit_notifier/test/fixtures/git_log b/test/fixtures/git_log similarity index 100% rename from git_commit_notifier/test/fixtures/git_log rename to test/fixtures/git_log diff --git a/git_commit_notifier/test/fixtures/git_show_51b986619d88f7ba98be7d271188785cbbb541a0 b/test/fixtures/git_show_51b986619d88f7ba98be7d271188785cbbb541a0 similarity index 100% rename from git_commit_notifier/test/fixtures/git_show_51b986619d88f7ba98be7d271188785cbbb541a0 rename to test/fixtures/git_show_51b986619d88f7ba98be7d271188785cbbb541a0 diff --git a/git_commit_notifier/test/fixtures/git_show_a4629e707d80a5769f7a71ca6ed9471015e14dc9 b/test/fixtures/git_show_a4629e707d80a5769f7a71ca6ed9471015e14dc9 similarity index 100% rename from git_commit_notifier/test/fixtures/git_show_a4629e707d80a5769f7a71ca6ed9471015e14dc9 rename to test/fixtures/git_show_a4629e707d80a5769f7a71ca6ed9471015e14dc9 diff --git a/git_commit_notifier/test/fixtures/git_show_dce6ade4cdc2833b53bd600ef10f9bce83c7102d b/test/fixtures/git_show_dce6ade4cdc2833b53bd600ef10f9bce83c7102d similarity index 100% rename from git_commit_notifier/test/fixtures/git_show_dce6ade4cdc2833b53bd600ef10f9bce83c7102d rename to test/fixtures/git_show_dce6ade4cdc2833b53bd600ef10f9bce83c7102d diff --git a/git_commit_notifier/test/fixtures/git_show_e28ad77bba0574241e6eb64dfd0c1291b221effe b/test/fixtures/git_show_e28ad77bba0574241e6eb64dfd0c1291b221effe similarity index 100% rename from git_commit_notifier/test/fixtures/git_show_e28ad77bba0574241e6eb64dfd0c1291b221effe rename to test/fixtures/git_show_e28ad77bba0574241e6eb64dfd0c1291b221effe diff --git a/git_commit_notifier/test/result_processor_test.rb b/test/result_processor_test.rb similarity index 100% rename from git_commit_notifier/test/result_processor_test.rb rename to test/result_processor_test.rb diff --git a/git_commit_notifier/test/test_helper.rb b/test/test_helper.rb similarity index 100% rename from git_commit_notifier/test/test_helper.rb rename to test/test_helper.rb