git-commit-notifier 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +22 -0
- data/README.textile +54 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/git-commit-notifier +14 -0
- data/config/git-notifier-configl.yml.sample +15 -0
- data/git-commit-notifier.gemspec +79 -0
- data/lib/commit_hook.rb +53 -0
- data/lib/diff_to_html.rb +321 -0
- data/lib/emailer.rb +102 -0
- data/lib/git.rb +35 -0
- data/lib/result_processor.rb +122 -0
- data/template/email.html.erb +9 -0
- data/template/styles.css +10 -0
- data/test/fixtures/git_log +34 -0
- data/test/fixtures/git_show_51b986619d88f7ba98be7d271188785cbbb541a0 +83 -0
- data/test/fixtures/git_show_a4629e707d80a5769f7a71ca6ed9471015e14dc9 +49 -0
- data/test/fixtures/git_show_dce6ade4cdc2833b53bd600ef10f9bce83c7102d +83 -0
- data/test/fixtures/git_show_e28ad77bba0574241e6eb64dfd0c1291b221effe +76 -0
- data/test/test_helper.rb +21 -0
- data/test/unit/test_commit_hook.rb +22 -0
- data/test/unit/test_diff_to_html.rb +97 -0
- data/test/unit/test_result_processor.rb +95 -0
- metadata +125 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2008 Csoma Zoltan, Primalgrasp
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
_h1. Git Commit Notifier
|
2
|
+
|
3
|
+
<pre>by Bodo Tasche (bodo 'at' wannawork 'dot' de), Csoma Zoltan (Primalgrasp) (zoltan 'at' primalgrasp 'dot' com)</pre>
|
4
|
+
|
5
|
+
Sends email commit messages splitting commits that were pushed in one
|
6
|
+
step. Email is delivered as text or HTML with changes refined per
|
7
|
+
word. Emails have a scanable subject containing the first sentence of
|
8
|
+
the commit as well as the author, project and branch name.
|
9
|
+
|
10
|
+
For example:
|
11
|
+
<pre>[rails][master] Fix Brasilia timezone. [#1180 state:resolved] </pre>
|
12
|
+
|
13
|
+
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:
|
14
|
+
<pre>
|
15
|
+
[rails][master][000] Added deprecated warning messages to Float#months and Float#years deprications.
|
16
|
+
[rails][master][001] Enhance testing for fractional days and weeks. Update changelog.
|
17
|
+
</pre>
|
18
|
+
|
19
|
+
Example email:
|
20
|
+
|
21
|
+
!http://img171.imageshack.us/img171/954/gitcommitnotifieremailpq3.png!
|
22
|
+
|
23
|
+
h1. Requirements
|
24
|
+
|
25
|
+
* Ruby
|
26
|
+
* RubyGems
|
27
|
+
* diff/lcs gem
|
28
|
+
* SMTP server or sendmail compatible mailer
|
29
|
+
* mocha, hpricot gems for testing
|
30
|
+
|
31
|
+
h1. Installing and Configuring
|
32
|
+
|
33
|
+
Before installing the Git Commit Notification script, make sure the following Git settings are correct:
|
34
|
+
* git config hooks.mailinglist (email address of the recipient, probably your mailing list address)
|
35
|
+
* git config hooks.emailprefix (application name, used in email subject)
|
36
|
+
|
37
|
+
See /usr/local/share/git_commit_notifier/config/config.yml for overriding these settings
|
38
|
+
|
39
|
+
Run the automated installation script:
|
40
|
+
<pre>sudo gem install git-commit-notifier</pre>
|
41
|
+
|
42
|
+
After you installed the gem, you need to configure your git repository. Add a File called "post-receive" to the __hooks__
|
43
|
+
directory and add this content:
|
44
|
+
|
45
|
+
<pre>
|
46
|
+
#!/bin/sh
|
47
|
+
git-commit-notifier path_to_config.yml
|
48
|
+
</pre>
|
49
|
+
|
50
|
+
And don't forget to make that file executable. An example for the config-file can be found in __/usr/local/share/git_commit_notifier/config/config.yml__ .
|
51
|
+
|
52
|
+
h1. License
|
53
|
+
|
54
|
+
MIT License, see the file LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "git-commit-notifier"
|
8
|
+
gem.summary = %Q{Sends git commit messages with diffs}
|
9
|
+
gem.description = %Q{This git commit notifier sends html mails with nice diffs for every changed file.}
|
10
|
+
gem.email = "bodo@wannawork.de"
|
11
|
+
gem.homepage = "http://github.com/bodo/git-commit-notifier"
|
12
|
+
gem.authors = ["Bodo Tasche"]
|
13
|
+
gem.add_dependency('diff-lcs')
|
14
|
+
gem.add_dependency('mocha')
|
15
|
+
gem.add_dependency('hpricot')
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'rake/testtask'
|
24
|
+
Rake::TestTask.new(:test) do |test|
|
25
|
+
test.libs << 'lib' << 'test'
|
26
|
+
test.pattern = 'test/**/test_*.rb'
|
27
|
+
test.verbose = true
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'rcov/rcovtask'
|
32
|
+
Rcov::RcovTask.new do |test|
|
33
|
+
test.libs << 'test'
|
34
|
+
test.pattern = 'test/**/test_*.rb'
|
35
|
+
test.verbose = true
|
36
|
+
end
|
37
|
+
rescue LoadError
|
38
|
+
task :rcov do
|
39
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
task :test => :check_dependencies
|
44
|
+
|
45
|
+
task :default => :test
|
46
|
+
|
47
|
+
require 'rake/rdoctask'
|
48
|
+
Rake::RDocTask.new do |rdoc|
|
49
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
50
|
+
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "git-commit-notifier #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# parameters: revision1, revision 2, branch
|
3
|
+
THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
4
|
+
$:.unshift File.join(File.dirname(THIS_FILE), "../lib")
|
5
|
+
require "commit_hook"
|
6
|
+
|
7
|
+
if ARGV.length == 0
|
8
|
+
CommitHook.show_error("You have to add a path to the config file for git-commit-notifier")
|
9
|
+
elsif ARGV.length == 1
|
10
|
+
param = STDIN.gets.strip.split
|
11
|
+
CommitHook.run ARGV[0], param[0], param[1], param[2]
|
12
|
+
else
|
13
|
+
CommitHook.run ARGV[0], ARGV[1], ARGV[2]
|
14
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{git-commit-notifier}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Bodo Tasche"]
|
12
|
+
s.date = %q{2010-03-24}
|
13
|
+
s.default_executable = %q{git-commit-notifier}
|
14
|
+
s.description = %q{This git commit notifier sends html mails with nice diffs for every changed file.}
|
15
|
+
s.email = %q{bodo@wannawork.de}
|
16
|
+
s.executables = ["git-commit-notifier"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README.textile"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".gitignore",
|
24
|
+
"LICENSE",
|
25
|
+
"README.textile",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"bin/git-commit-notifier",
|
29
|
+
"config/git-notifier-configl.yml.sample",
|
30
|
+
"git-commit-notifier.gemspec",
|
31
|
+
"lib/commit_hook.rb",
|
32
|
+
"lib/diff_to_html.rb",
|
33
|
+
"lib/emailer.rb",
|
34
|
+
"lib/git.rb",
|
35
|
+
"lib/result_processor.rb",
|
36
|
+
"template/email.html.erb",
|
37
|
+
"template/styles.css",
|
38
|
+
"test/fixtures/git_log",
|
39
|
+
"test/fixtures/git_show_51b986619d88f7ba98be7d271188785cbbb541a0",
|
40
|
+
"test/fixtures/git_show_a4629e707d80a5769f7a71ca6ed9471015e14dc9",
|
41
|
+
"test/fixtures/git_show_dce6ade4cdc2833b53bd600ef10f9bce83c7102d",
|
42
|
+
"test/fixtures/git_show_e28ad77bba0574241e6eb64dfd0c1291b221effe",
|
43
|
+
"test/test_helper.rb",
|
44
|
+
"test/unit/test_commit_hook.rb",
|
45
|
+
"test/unit/test_diff_to_html.rb",
|
46
|
+
"test/unit/test_result_processor.rb"
|
47
|
+
]
|
48
|
+
s.homepage = %q{http://github.com/bodo/git-commit-notifier}
|
49
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
50
|
+
s.require_paths = ["lib"]
|
51
|
+
s.rubygems_version = %q{1.3.6}
|
52
|
+
s.summary = %q{Sends git commit messages with diffs}
|
53
|
+
s.test_files = [
|
54
|
+
"test/test_helper.rb",
|
55
|
+
"test/unit/test_commit_hook.rb",
|
56
|
+
"test/unit/test_diff_to_html.rb",
|
57
|
+
"test/unit/test_result_processor.rb"
|
58
|
+
]
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
62
|
+
s.specification_version = 3
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
65
|
+
s.add_runtime_dependency(%q<diff-lcs>, [">= 0"])
|
66
|
+
s.add_runtime_dependency(%q<mocha>, [">= 0"])
|
67
|
+
s.add_runtime_dependency(%q<hpricot>, [">= 0"])
|
68
|
+
else
|
69
|
+
s.add_dependency(%q<diff-lcs>, [">= 0"])
|
70
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
71
|
+
s.add_dependency(%q<hpricot>, [">= 0"])
|
72
|
+
end
|
73
|
+
else
|
74
|
+
s.add_dependency(%q<diff-lcs>, [">= 0"])
|
75
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
76
|
+
s.add_dependency(%q<hpricot>, [">= 0"])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
data/lib/commit_hook.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'cgi'
|
3
|
+
require 'net/smtp'
|
4
|
+
require 'sha1'
|
5
|
+
|
6
|
+
require 'diff_to_html'
|
7
|
+
require 'emailer'
|
8
|
+
require 'git'
|
9
|
+
|
10
|
+
class CommitHook
|
11
|
+
|
12
|
+
def self.show_error(message)
|
13
|
+
puts "************** GIT NOTIFIER PROBLEM *******************"
|
14
|
+
puts "\n"
|
15
|
+
puts message
|
16
|
+
puts "\n"
|
17
|
+
puts "************** GIT NOTIFIER PROBLEM *******************"
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.run(config, rev1, rev2, ref_name)
|
21
|
+
project_path = Dir.getwd
|
22
|
+
recipient = Git.mailing_list_address
|
23
|
+
|
24
|
+
if (recipient.nil? || recipient.length == 0)
|
25
|
+
CommitHook.show_error(
|
26
|
+
"Please add a recipient for the emails. Eg : \n" +
|
27
|
+
" git config hooks.mailinglist developer@example.com")
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
prefix = Git.repo_name
|
32
|
+
branch_name = (ref_name =~ /master$/i) ? "" : "/#{ref_name.split("/").last}"
|
33
|
+
|
34
|
+
@config = {}
|
35
|
+
@config = YAML::load_file(config) if File.exist?(config)
|
36
|
+
|
37
|
+
diff2html = DiffToHtml.new(Dir.pwd)
|
38
|
+
diff2html.diff_between_revisions rev1, rev2, prefix, ref_name
|
39
|
+
diff2html.result.reverse.each_with_index do |result, i|
|
40
|
+
nr = number(diff2html.result.size, i)
|
41
|
+
emailer = Emailer.new @config, project_path, recipient, result[:commit_info][:email], result[:commit_info][:author],
|
42
|
+
"[#{prefix}#{branch_name}]#{nr} #{result[:commit_info][:message]}", result[:text_content], result[:html_content], rev1, rev2, ref_name
|
43
|
+
emailer.send
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.number(total_entries, i)
|
48
|
+
return '' if total_entries <= 1
|
49
|
+
digits = total_entries < 10 ? 1 : 3
|
50
|
+
'[' + sprintf("%0#{digits}d", i) + ']'
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/diff_to_html.rb
ADDED
@@ -0,0 +1,321 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'diff/lcs'
|
3
|
+
require File.dirname(__FILE__) + '/result_processor'
|
4
|
+
|
5
|
+
def escape_content(s)
|
6
|
+
CGI.escapeHTML(s).gsub(" ", " ")
|
7
|
+
end
|
8
|
+
|
9
|
+
class DiffToHtml
|
10
|
+
attr_accessor :file_prefix
|
11
|
+
attr_reader :result
|
12
|
+
|
13
|
+
def initialize(config_dir = nil)
|
14
|
+
@previous_dir = config_dir
|
15
|
+
end
|
16
|
+
|
17
|
+
def range_info(range)
|
18
|
+
range.match(/^@@ \-(\d+),\d+ \+(\d+),\d+ @@/)
|
19
|
+
left_ln = Integer($1)
|
20
|
+
right_ln = Integer($2)
|
21
|
+
return left_ln, right_ln
|
22
|
+
end
|
23
|
+
|
24
|
+
def line_class(line)
|
25
|
+
if line[:op] == :removal
|
26
|
+
return " class=\"r\""
|
27
|
+
elsif line[:op] == :addition
|
28
|
+
return " class=\"a\""
|
29
|
+
else
|
30
|
+
return ''
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_block_to_results(block, escape)
|
35
|
+
return if block.empty?
|
36
|
+
block.each do |line|
|
37
|
+
add_line_to_result(line, escape)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_line_to_result(line, escape)
|
42
|
+
klass = line_class(line)
|
43
|
+
content = escape ? escape_content(line[:content]) : line[:content]
|
44
|
+
padding = ' ' if klass != ''
|
45
|
+
@diff_result << "<tr#{klass}>\n<td class=\"ln\">#{line[:removed]}</td>\n<td class=\"ln\">#{line[:added]}</td>\n<td>#{padding}#{content}</td></tr>"
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_block_content(block)
|
49
|
+
block.collect { |b| b[:content]}.join("\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
def lcs_diff(removals, additions)
|
53
|
+
# arrays always have at least 1 element
|
54
|
+
callback = DiffCallback.new
|
55
|
+
|
56
|
+
s1 = extract_block_content(removals)
|
57
|
+
s2 = extract_block_content(additions)
|
58
|
+
|
59
|
+
s1 = tokenize_string(s1)
|
60
|
+
s2 = tokenize_string(s2)
|
61
|
+
|
62
|
+
Diff::LCS.traverse_balanced(s1, s2, callback)
|
63
|
+
|
64
|
+
processor = ResultProcessor.new(callback.tags)
|
65
|
+
|
66
|
+
diff_for_removals, diff_for_additions = processor.results
|
67
|
+
result = []
|
68
|
+
|
69
|
+
ln_start = removals[0][:removed]
|
70
|
+
diff_for_removals.each_with_index do |line, i|
|
71
|
+
result << { :removed => ln_start + i, :added => nil, :op => :removal, :content => line}
|
72
|
+
end
|
73
|
+
|
74
|
+
ln_start = additions[0][:added]
|
75
|
+
diff_for_additions.each_with_index do |line, i|
|
76
|
+
result << { :removed => nil, :added => ln_start + i, :op => :addition, :content => line}
|
77
|
+
end
|
78
|
+
|
79
|
+
result
|
80
|
+
end
|
81
|
+
|
82
|
+
def tokenize_string(str)
|
83
|
+
# tokenize by non-alphanumerical characters
|
84
|
+
tokens = []
|
85
|
+
token = ''
|
86
|
+
str = str.split('')
|
87
|
+
str.each_with_index do |char, i|
|
88
|
+
alphanumeric = !char.match(/[a-zA-Z0-9]/).nil?
|
89
|
+
if !alphanumeric || str.size == i+1
|
90
|
+
token += char if alphanumeric
|
91
|
+
tokens << token unless token.empty?
|
92
|
+
tokens << char unless alphanumeric
|
93
|
+
token = ''
|
94
|
+
else
|
95
|
+
token += char
|
96
|
+
end
|
97
|
+
end
|
98
|
+
return tokens
|
99
|
+
end
|
100
|
+
|
101
|
+
def operation_description
|
102
|
+
binary = @binary ? 'binary ' : ''
|
103
|
+
if @file_removed
|
104
|
+
op = "Deleted"
|
105
|
+
elsif @file_added
|
106
|
+
op = "Added"
|
107
|
+
else
|
108
|
+
op = "Changed"
|
109
|
+
end
|
110
|
+
header = "#{op} #{binary}file #{@current_file_name}"
|
111
|
+
"<h2>#{header}</h2>\n"
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_changes_to_result
|
115
|
+
return if @current_file_name.nil?
|
116
|
+
@diff_result << operation_description
|
117
|
+
@diff_result << '<table>'
|
118
|
+
unless @diff_lines.empty?
|
119
|
+
removals = []
|
120
|
+
additions = []
|
121
|
+
@diff_lines.each do |line|
|
122
|
+
if [:addition, :removal].include?(line[:op])
|
123
|
+
removals << line if line[:op] == :removal
|
124
|
+
additions << line if line[:op] == :addition
|
125
|
+
end
|
126
|
+
if line[:op] == :unchanged || line == @diff_lines.last # unchanged line or end of block, add prev lines to result
|
127
|
+
if removals.size > 0 && additions.size > 0 # block of removed and added lines - perform intelligent diff
|
128
|
+
add_block_to_results(lcs_diff(removals, additions), escape = false)
|
129
|
+
else # some lines removed or added - no need to perform intelligent diff
|
130
|
+
add_block_to_results(removals + additions, escape = true)
|
131
|
+
end
|
132
|
+
removals = []
|
133
|
+
additions = []
|
134
|
+
add_line_to_result(line, escape = true) if line[:op] == :unchanged
|
135
|
+
end
|
136
|
+
end
|
137
|
+
@diff_lines = []
|
138
|
+
@diff_result << '</table>'
|
139
|
+
end
|
140
|
+
# reset values
|
141
|
+
@right_ln = nil
|
142
|
+
@left_ln = nil
|
143
|
+
@file_added = false
|
144
|
+
@file_removed = false
|
145
|
+
@binary = false
|
146
|
+
end
|
147
|
+
|
148
|
+
def diff_for_revision(content)
|
149
|
+
@left_ln = @right_ln = nil
|
150
|
+
|
151
|
+
@diff_result = []
|
152
|
+
@diff_lines = []
|
153
|
+
@removed_files = []
|
154
|
+
@current_file_name = nil
|
155
|
+
|
156
|
+
content.split("\n").each do |line|
|
157
|
+
if line =~ /^diff\s\-\-git/
|
158
|
+
line.match(/diff --git a\/(.*)\sb\//)
|
159
|
+
file_name = $1
|
160
|
+
add_changes_to_result
|
161
|
+
@current_file_name = file_name
|
162
|
+
end
|
163
|
+
|
164
|
+
op = line[0,1]
|
165
|
+
@left_ln.nil? || op == '@' ? process_info_line(line, op) : process_code_line(line, op)
|
166
|
+
end
|
167
|
+
add_changes_to_result
|
168
|
+
@diff_result.join("\n")
|
169
|
+
end
|
170
|
+
|
171
|
+
def process_code_line(line, op)
|
172
|
+
if op == '-'
|
173
|
+
@diff_lines << { :removed => @left_ln, :added => nil, :op => :removal, :content => line[1..-1] }
|
174
|
+
@left_ln += 1
|
175
|
+
elsif op == '+'
|
176
|
+
@diff_lines << { :added => @right_ln, :removed => nil, :op => :addition, :content => line[1..-1] }
|
177
|
+
@right_ln += 1
|
178
|
+
else @right_ln
|
179
|
+
@diff_lines << { :added => @right_ln, :removed => @left_ln, :op => :unchanged, :content => line }
|
180
|
+
@right_ln += 1
|
181
|
+
@left_ln += 1
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def process_info_line(line, op)
|
186
|
+
if line =~/^deleted\sfile\s/
|
187
|
+
@file_removed = true
|
188
|
+
elsif line =~ /^\-\-\-\s/ && line =~ /\/dev\/null/
|
189
|
+
@file_added = true
|
190
|
+
elsif line =~ /^\+\+\+\s/ && line =~ /\/dev\/null/
|
191
|
+
@file_removed = true
|
192
|
+
elsif line =~ /^Binary files \/dev\/null/ # Binary files /dev/null and ... differ (addition)
|
193
|
+
@binary = true
|
194
|
+
@file_added = true
|
195
|
+
elsif line =~ /\/dev\/null differ/ # Binary files ... and /dev/null differ (removal)
|
196
|
+
@binary = true
|
197
|
+
@file_removed = true
|
198
|
+
elsif op == '@'
|
199
|
+
@left_ln, @right_ln = range_info(line)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def extract_diff_from_git_show_output(content)
|
204
|
+
diff = []
|
205
|
+
diff_found = false
|
206
|
+
content.split("\n").each do |line|
|
207
|
+
diff_found = true if line =~ /^diff \-\-git/
|
208
|
+
next unless diff_found
|
209
|
+
diff << line
|
210
|
+
end
|
211
|
+
diff.join("\n")
|
212
|
+
end
|
213
|
+
|
214
|
+
def extract_commit_info_from_git_show_output(content)
|
215
|
+
result = { :message => [], :commit => '', :author => '', :date => '', :email => '' }
|
216
|
+
content.split("\n").each do |line|
|
217
|
+
if line =~ /^diff/ # end of commit info, return results
|
218
|
+
return result
|
219
|
+
elsif line =~ /^commit/
|
220
|
+
result[:commit] = line[7..-1]
|
221
|
+
elsif line =~ /^Author/
|
222
|
+
result[:author], result[:email] = author_name_and_email(line[8..-1])
|
223
|
+
elsif line =~ /^Date/
|
224
|
+
result[:date] = line[8..-1]
|
225
|
+
else
|
226
|
+
clean_line = line.strip
|
227
|
+
result[:message] << clean_line unless clean_line.empty?
|
228
|
+
end
|
229
|
+
end
|
230
|
+
result
|
231
|
+
end
|
232
|
+
|
233
|
+
def message_array_as_html(message)
|
234
|
+
message.collect { |m| CGI.escapeHTML(m)}.join("<br />")
|
235
|
+
end
|
236
|
+
|
237
|
+
def author_name_and_email(info)
|
238
|
+
# input string format: "autor name <author@email.net>"
|
239
|
+
result = info.scan(/(.*)\s<(.*)>/)[0]
|
240
|
+
return result if result.is_a?(Array) && result.size == 2 # normal operation
|
241
|
+
# incomplete author info - return it as author name
|
242
|
+
return [info, ''] if result.nil?
|
243
|
+
end
|
244
|
+
|
245
|
+
def first_sentence(message_array)
|
246
|
+
msg = message_array.first.to_s.strip
|
247
|
+
return message_array.first if msg.empty? || msg =~ /^Merge\:/
|
248
|
+
msg
|
249
|
+
end
|
250
|
+
|
251
|
+
def diff_between_revisions(rev1, rev2, repo, branch)
|
252
|
+
@result = []
|
253
|
+
if rev1 == rev2
|
254
|
+
commits = [rev1]
|
255
|
+
elsif rev1 =~ /^0+$/
|
256
|
+
# creating a new remote branch
|
257
|
+
commits = Git.branch_commits(branch)
|
258
|
+
elsif rev2 =~ /^0+$/
|
259
|
+
# deleting an existing remote branch
|
260
|
+
commits = []
|
261
|
+
else
|
262
|
+
log = Git.log(rev1, rev2)
|
263
|
+
commits = log.scan(/^commit\s([a-f0-9]+)/).map{|match| match[0]}
|
264
|
+
end
|
265
|
+
|
266
|
+
if defined?(Test::Unit)
|
267
|
+
previous_list = []
|
268
|
+
else
|
269
|
+
previous_file = (!@previous_dir.nil? && File.exists?(@previous_dir)) ? File.join(@previous_dir, "previously.txt") : "/tmp/previously.txt"
|
270
|
+
previous_list = (File.read(previous_file).to_a.map {|sha| sha.chomp!} if File.exist?(previous_file)) || []
|
271
|
+
end
|
272
|
+
|
273
|
+
commits.reject!{|c| c.find{|sha| previous_list.include?(sha)} }
|
274
|
+
current_list = (previous_list + commits.flatten).last(1000)
|
275
|
+
File.open(previous_file, "w"){|f| f << current_list.join("\n") } unless current_list.empty? || defined?(Test::Unit)
|
276
|
+
|
277
|
+
commits.each_with_index do |commit, i|
|
278
|
+
raw_diff = Git.show(commit)
|
279
|
+
raise "git show output is empty" if raw_diff.empty?
|
280
|
+
@last_raw = raw_diff
|
281
|
+
|
282
|
+
commit_info = extract_commit_info_from_git_show_output(raw_diff)
|
283
|
+
|
284
|
+
title = "<div class=\"title\">"
|
285
|
+
title += "<strong>Message:</strong> #{message_array_as_html commit_info[:message]}<br />\n"
|
286
|
+
title += "<strong>Commit</strong> #{commit_info[:commit]}<br />\n"
|
287
|
+
title += "<strong>Branch:</strong> #{branch}\n<br />" unless branch =~ /\/head/
|
288
|
+
title += "<strong>Date:</strong> #{CGI.escapeHTML commit_info[:date]}\n<br />"
|
289
|
+
title += "<strong>Author:</strong> #{CGI.escapeHTML(commit_info[:author])} <#{commit_info[:email]}>\n</div>"
|
290
|
+
|
291
|
+
text = "#{raw_diff}\n\n\n"
|
292
|
+
|
293
|
+
html = title
|
294
|
+
html += diff_for_revision(extract_diff_from_git_show_output(raw_diff))
|
295
|
+
html += "<br /><br />"
|
296
|
+
commit_info[:message] = first_sentence(commit_info[:message])
|
297
|
+
@result << {:commit_info => commit_info, :html_content => html, :text_content => text }
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
class DiffCallback
|
303
|
+
attr_reader :tags
|
304
|
+
|
305
|
+
def initialize
|
306
|
+
@tags = []
|
307
|
+
end
|
308
|
+
|
309
|
+
def match(event)
|
310
|
+
@tags << { :action => :match, :token => event.old_element }
|
311
|
+
end
|
312
|
+
|
313
|
+
def discard_b(event)
|
314
|
+
@tags << { :action => :discard_b, :token => event.new_element }
|
315
|
+
end
|
316
|
+
|
317
|
+
def discard_a(event)
|
318
|
+
@tags << { :action => :discard_a, :token => event.old_element }
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|