git-commit-notifier 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|