subtrigger 0.2.7 → 0.3.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/.gitignore +2 -0
- data/README.md +158 -0
- data/Rakefile +3 -9
- data/VERSION +1 -1
- data/lib/subtrigger.rb +83 -118
- data/lib/subtrigger/dsl.rb +42 -0
- data/lib/subtrigger/path.rb +82 -0
- data/lib/subtrigger/revision.rb +95 -0
- data/lib/subtrigger/rule.rb +165 -0
- data/lib/subtrigger/template.rb +96 -0
- data/subtrigger.gemspec +24 -24
- data/test/test_helper.rb +3 -0
- data/test/test_path.rb +50 -0
- data/test/test_revision.rb +43 -0
- data/test/test_rule.rb +66 -0
- data/test/test_template.rb +42 -0
- metadata +48 -26
- data/LICENSE +0 -20
- data/README.rdoc +0 -56
- data/bin/subtrigger +0 -9
- data/lib/subtrigger/email.rb +0 -56
- data/lib/subtrigger/repository.rb +0 -140
- data/lib/subtrigger/trigger.rb +0 -60
- data/test/helper.rb +0 -11
- data/test/test_email.rb +0 -44
- data/test/test_repository.rb +0 -75
- data/test/test_subtrigger.rb +0 -44
- data/test/test_trigger.rb +0 -37
data/lib/subtrigger/email.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
module Subtrigger
|
2
|
-
# = E-mail notifications
|
3
|
-
#
|
4
|
-
# Sometimes you want to send notification e-mails after the hook has fired
|
5
|
-
# to inform developers or yourself of some event. This class is a simple
|
6
|
-
# wrapper around the standard +sendmail+ program.
|
7
|
-
#
|
8
|
-
# == Usage example
|
9
|
-
#
|
10
|
-
# Email.new(:to => 'john@cleese.com',
|
11
|
-
# :from => 'eric@idle.com',
|
12
|
-
# :subject => 'Fired',
|
13
|
-
# :body => 'Your post-commit hook has just fired').send
|
14
|
-
#
|
15
|
-
# If +sendmail+ can not be found on your system an exception will be raised.
|
16
|
-
class Email
|
17
|
-
attr_accessor :from, :to, :subject, :body, :development
|
18
|
-
attr_reader :sendmail
|
19
|
-
|
20
|
-
# Sets up a new message and tries to find +sendmail+ on your system.
|
21
|
-
def initialize(options = {})
|
22
|
-
@to = options[:to]
|
23
|
-
@from = options[:from]
|
24
|
-
@subject = options[:subject]
|
25
|
-
@body = options[:body]
|
26
|
-
@development = options[:development] || false
|
27
|
-
@sendmail = Subtrigger.sendmail || `which sendmail`.strip
|
28
|
-
raise 'Could not find sendmail; aborting.' if @sendmail.nil?
|
29
|
-
end
|
30
|
-
|
31
|
-
# Tries to use +sendmail+ to send the message.
|
32
|
-
# The message sent is returned for inspecting purposes.
|
33
|
-
def send
|
34
|
-
message = header + "\n" + body
|
35
|
-
unless development
|
36
|
-
fd = open("|#{sendmail} #{to}", "w")
|
37
|
-
fd.print(message)
|
38
|
-
fd.close
|
39
|
-
end
|
40
|
-
message
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def header
|
46
|
-
<<-EOS
|
47
|
-
To: #{@to}
|
48
|
-
From: #{@from}
|
49
|
-
Subject: [svn] #{@subject}
|
50
|
-
MIME-version: 1.0
|
51
|
-
Content-Type: text/plain; charset=UTF-8
|
52
|
-
Content-Transfer-Encoding: 8bit
|
53
|
-
EOS
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
@@ -1,140 +0,0 @@
|
|
1
|
-
module Subtrigger
|
2
|
-
# = Subversion repostitory wrapper
|
3
|
-
#
|
4
|
-
# Use this class to get to the information for a specific commit in a
|
5
|
-
# specific subversion repository. This is a simple wrapper around the
|
6
|
-
# +svnlook+ command.
|
7
|
-
#
|
8
|
-
# This class will look for +svn+ and +svnlook+ on your system and will raise
|
9
|
-
# an exception when it can not be found.
|
10
|
-
#
|
11
|
-
# == Usage example
|
12
|
-
#
|
13
|
-
# repo = Repository.new('/path/to/repo', 5540)
|
14
|
-
# repo.author # => 'Graham'
|
15
|
-
# repo.message # => 'Added copyright information to the readme'
|
16
|
-
# repo.changed_projects do |p|
|
17
|
-
# puts p # => a changed directory above a trunk, branches or tags
|
18
|
-
# end
|
19
|
-
#
|
20
|
-
class Repository
|
21
|
-
attr_reader :path, :revision
|
22
|
-
|
23
|
-
# Initialize a new wrapper around a repository at a given revision.
|
24
|
-
#
|
25
|
-
# This will try to find the +svn+ executable on your system and raise
|
26
|
-
# an exception when it cannot be found.
|
27
|
-
#
|
28
|
-
# Exceptions will also be raised when the repository path can not be
|
29
|
-
# found or the revision is not numeric.
|
30
|
-
def initialize(path, revision)
|
31
|
-
raise "Repository '#{path}' not found" unless File.directory?(path)
|
32
|
-
raise "Invalid revision number '#{revision}'" unless revision.to_i > 0
|
33
|
-
@path = path
|
34
|
-
@revision = revision.to_i
|
35
|
-
@svn_path = Subtrigger.svn || `which svn`.strip
|
36
|
-
raise 'Could not locate svn' if @svn_path.nil?
|
37
|
-
end
|
38
|
-
|
39
|
-
# Return the path to the current repository. If given an extra string,
|
40
|
-
# that will be appended to the path.
|
41
|
-
#
|
42
|
-
# Example:
|
43
|
-
#
|
44
|
-
# repo.path # => '/path/to/repo'
|
45
|
-
# repo.path('mydir') # => '/path/to/repo/mydir'
|
46
|
-
#
|
47
|
-
def path(subpath = nil)
|
48
|
-
return File.join(@path, subpath) unless subpath.nil?
|
49
|
-
@path
|
50
|
-
end
|
51
|
-
|
52
|
-
# Returns the information from <tt>svnlook changed</tt>.
|
53
|
-
def changed
|
54
|
-
@changed ||= look_at('changed')
|
55
|
-
end
|
56
|
-
|
57
|
-
# Yields all directories above a changed trunk, branches or tags directory.
|
58
|
-
#
|
59
|
-
# Assuming a project layout like this:
|
60
|
-
#
|
61
|
-
# [root]
|
62
|
-
# |- project1
|
63
|
-
# |- project2
|
64
|
-
# |- group1
|
65
|
-
# `- project3
|
66
|
-
# |- branches
|
67
|
-
# |- tags
|
68
|
-
# `- trunk
|
69
|
-
#
|
70
|
-
# Then committing to <tt>group1/project3/trunk</tt> will yield both
|
71
|
-
# <tt>group1/project3/trunk</tt> and <tt>project3</tt>.
|
72
|
-
#
|
73
|
-
# The list of changed project names is returned as an array.
|
74
|
-
def changed_projects #:yields: full_path, project_path
|
75
|
-
projects = []
|
76
|
-
(@dirs_changed ||= look_at('dirs-changed')).split("\n").each do |dir|
|
77
|
-
if dir =~ /([\w\-\.]+)\/(trunk|branches|tags)/
|
78
|
-
project_name = $1
|
79
|
-
top_level_dir = case dir
|
80
|
-
when /^(.*\/trunk)/: $1
|
81
|
-
when /^(.*\/(?:tags|branches)\/[\w\-]+)/: $1
|
82
|
-
end
|
83
|
-
projects << project_name
|
84
|
-
yield top_level_dir, project_name if block_given?
|
85
|
-
end
|
86
|
-
end
|
87
|
-
projects
|
88
|
-
end
|
89
|
-
|
90
|
-
# Returns the HEAD revision number (<tt>svnlook youngest</tt>)
|
91
|
-
def head
|
92
|
-
@head ||= look_at('youngest')
|
93
|
-
end
|
94
|
-
|
95
|
-
# Returns the author of the last commit.
|
96
|
-
def author
|
97
|
-
@author ||= get_line_from_info(0)
|
98
|
-
end
|
99
|
-
|
100
|
-
# Returns the log message of the last commit.
|
101
|
-
def message
|
102
|
-
@message ||= get_line_from_info(3)
|
103
|
-
end
|
104
|
-
|
105
|
-
# Returns the date from the last commit.
|
106
|
-
def date
|
107
|
-
@date ||= get_line_from_info(1)
|
108
|
-
end
|
109
|
-
|
110
|
-
# Runs an arbitrary +svn+ command and returns its results.
|
111
|
-
def exec(command)
|
112
|
-
command = "#{@svn_path} #{command} #{Subtrigger.svn_args}"
|
113
|
-
`#{command}`
|
114
|
-
end
|
115
|
-
|
116
|
-
private
|
117
|
-
|
118
|
-
# Execute a +svnlook+ command for the current repository and revision.
|
119
|
-
def look_at(subcommand)
|
120
|
-
`#{File.join(File.dirname(@svn_path), 'svnlook')} #{subcommand} #{@path} -r #{@revision}`
|
121
|
-
end
|
122
|
-
|
123
|
-
# Get the contents of a line from the <tt>svnlook info</tt> output, which
|
124
|
-
# looks like this:
|
125
|
-
#
|
126
|
-
# Author name
|
127
|
-
# Commit date
|
128
|
-
# Log message length
|
129
|
-
# Log message
|
130
|
-
#
|
131
|
-
# Note that the log messages might have multiple lines. Asking for line 3
|
132
|
-
# (actually 4, counting from 0) will give you that line and all lines
|
133
|
-
# after that.
|
134
|
-
def get_line_from_info(n)
|
135
|
-
@info ||= look_at('info')
|
136
|
-
parts = @info.split("\n")
|
137
|
-
n = n >= 3 ? parts[3..parts.size].join("\n") : parts[n]
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
data/lib/subtrigger/trigger.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
module Subtrigger
|
2
|
-
# = Call blocks on a matching pattern
|
3
|
-
#
|
4
|
-
# This is a framework for combining pairs of matchers and callbacks to run
|
5
|
-
# on a commit message. You can define a trigger which this class will
|
6
|
-
# apply to a log message.
|
7
|
-
#
|
8
|
-
# == Example usage
|
9
|
-
#
|
10
|
-
# Trigger.define(/foo/) do |matches, repo|
|
11
|
-
# puts "Someone used 'foo' in his commit message"
|
12
|
-
# end
|
13
|
-
#
|
14
|
-
# When the above trigger is defined and somebody makes a commit message
|
15
|
-
# containing +foo+ the block will be called. +matches+ contains any
|
16
|
-
# captured regular expression groups, +repo+ is a +Repository+ object for
|
17
|
-
# the current repository revision.
|
18
|
-
#
|
19
|
-
# You can define as many triggers as you like. When no triggers are found
|
20
|
-
# an exception will be raised. When no trigger applies, it will quit
|
21
|
-
# silently.
|
22
|
-
class Trigger
|
23
|
-
class << self
|
24
|
-
@triggers = {}
|
25
|
-
|
26
|
-
# Run all available triggers on the given Repository object.
|
27
|
-
def run(repo)
|
28
|
-
raise 'No suitable triggers found.' if @triggers.nil?
|
29
|
-
@triggers.each_pair do |pattern, block|
|
30
|
-
new(pattern, repo, &block)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def triggers
|
35
|
-
@triggers ||= {}
|
36
|
-
end
|
37
|
-
|
38
|
-
def reset
|
39
|
-
@triggers = {}
|
40
|
-
end
|
41
|
-
|
42
|
-
# Create a new Trigger object and add it to the stack.
|
43
|
-
def define(pattern, &block)
|
44
|
-
(@triggers ||= {})[pattern] = block;
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def initialize(pattern, repo, &block)
|
49
|
-
@pattern, @repo, @callback = pattern, repo, block
|
50
|
-
parse
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
# Scan the commit message and fire the callback if it matches.
|
56
|
-
def parse
|
57
|
-
@repo.message.scan(@pattern) { |match| @callback.call(match, @repo) }
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
data/test/helper.rb
DELETED
data/test/test_email.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
|
-
class TestEmail < Test::Unit::TestCase
|
4
|
-
context 'with attributes' do
|
5
|
-
setup do
|
6
|
-
@email = Subtrigger::Email.new(
|
7
|
-
:to => 'to@to.com',
|
8
|
-
:from => 'from@from.com',
|
9
|
-
:subject => 'subject',
|
10
|
-
:body => 'body',
|
11
|
-
:development => true
|
12
|
-
)
|
13
|
-
@message = @email.send
|
14
|
-
end
|
15
|
-
|
16
|
-
should 'use custom sendmail location' do
|
17
|
-
Subtrigger.sendmail = 'foo'
|
18
|
-
assert_equal('foo', Subtrigger::Email.new.sendmail)
|
19
|
-
end
|
20
|
-
|
21
|
-
should 'set all e-mail attributes' do
|
22
|
-
assert_equal('from@from.com', @email.from)
|
23
|
-
assert_equal('to@to.com', @email.to)
|
24
|
-
assert_equal('subject', @email.subject)
|
25
|
-
assert_equal('body', @email.body)
|
26
|
-
end
|
27
|
-
|
28
|
-
should 'use to address' do
|
29
|
-
assert_match(/From: from@from.com/, @message)
|
30
|
-
end
|
31
|
-
|
32
|
-
should 'use from address' do
|
33
|
-
assert_match(/To: to@to.com/, @message)
|
34
|
-
end
|
35
|
-
|
36
|
-
should 'use subject' do
|
37
|
-
assert_match(/Subject: \[svn\] subject/, @message)
|
38
|
-
end
|
39
|
-
|
40
|
-
should 'use message body' do
|
41
|
-
assert_match(/body/, @message)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
data/test/test_repository.rb
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
|
-
class TestRepository < Test::Unit::TestCase
|
4
|
-
should 'not work on non-existant repo' do
|
5
|
-
assert_raise(RuntimeError) { Subtrigger::Repository.new('foo', 'bar') }
|
6
|
-
end
|
7
|
-
|
8
|
-
context 'for a repository' do
|
9
|
-
setup do
|
10
|
-
File.stubs(:directory?).returns(true)
|
11
|
-
@r = Subtrigger::Repository.new('path/to/repo', 1)
|
12
|
-
end
|
13
|
-
|
14
|
-
should 'not work on illegal revision' do
|
15
|
-
assert_raise(RuntimeError) { Subtrigger::Repository.new('foo', 'bar') }
|
16
|
-
end
|
17
|
-
|
18
|
-
should 'expand path' do
|
19
|
-
assert_equal('path/to/repo/foo', @r.path('foo'))
|
20
|
-
end
|
21
|
-
|
22
|
-
should 'read all lines of message from info' do
|
23
|
-
@r.expects(:look_at).with('info').returns("Arjan\ndate\n100\nFoo\nBar\nBaz")
|
24
|
-
assert_equal("Foo\nBar\nBaz", @r.message)
|
25
|
-
end
|
26
|
-
|
27
|
-
should 'use svnlook info' do
|
28
|
-
@r.expects(:look_at).with('info').returns('Foo')
|
29
|
-
assert_equal('Foo', @r.author)
|
30
|
-
end
|
31
|
-
|
32
|
-
should 'use custom configuration' do
|
33
|
-
Subtrigger::Repository.any_instance.expects(:`).with('/usr/foo/svn info --non-interactive').once
|
34
|
-
Subtrigger::Repository.any_instance.expects(:`).with('/usr/foo/svnlook info path/to/repo -r 1').once.returns('')
|
35
|
-
Subtrigger.svn = '/usr/foo/svn'
|
36
|
-
Subtrigger.svn_args = '--non-interactive'
|
37
|
-
@r = Subtrigger::Repository.new('path/to/repo', 1)
|
38
|
-
@r.exec('info')
|
39
|
-
@r.author
|
40
|
-
end
|
41
|
-
|
42
|
-
should 'return changed project names' do
|
43
|
-
lines = <<-EOS
|
44
|
-
www.project1.com/trunk
|
45
|
-
www.project2.com/branches/FB-feature
|
46
|
-
sub/www.project2.com/tags/v1
|
47
|
-
sub/www.project2.com/tags/v1/test
|
48
|
-
EOS
|
49
|
-
@r.expects(:look_at).with('dirs-changed').returns(lines)
|
50
|
-
assert_equal(['www.project1.com', 'www.project2.com', 'www.project2.com', 'www.project2.com'], @r.changed_projects)
|
51
|
-
end
|
52
|
-
|
53
|
-
should 'yield changed directories' do
|
54
|
-
lines = <<-EOS
|
55
|
-
www.project1.com/trunk
|
56
|
-
www.project2.com/branches/FB-feature
|
57
|
-
sub/www.project2.com/tags/v1
|
58
|
-
sub/www.project2.com/tags/v1/test
|
59
|
-
EOS
|
60
|
-
@r.expects(:look_at).with('dirs-changed').returns(lines)
|
61
|
-
yieldings = [
|
62
|
-
['www.project1.com/trunk', 'www.project1.com'],
|
63
|
-
['www.project2.com/branches/FB-feature', 'www.project2.com'],
|
64
|
-
['sub/www.project2.com/tags/v1', 'www.project2.com'],
|
65
|
-
['sub/www.project2.com/tags/v1', 'www.project2.com']
|
66
|
-
]
|
67
|
-
i = 0
|
68
|
-
@r.changed_projects do |path, project|
|
69
|
-
assert_equal(yieldings[i][0], path)
|
70
|
-
assert_equal(yieldings[i][1], project)
|
71
|
-
i += 1
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
data/test/test_subtrigger.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
|
-
class TestSubtrigger < Test::Unit::TestCase
|
4
|
-
context 'with a clean slate' do
|
5
|
-
setup do
|
6
|
-
Subtrigger.reset
|
7
|
-
Subtrigger::Trigger.reset
|
8
|
-
end
|
9
|
-
|
10
|
-
should 'output the version number' do
|
11
|
-
assert_match(/\d+\.\d+\.\d+/, Subtrigger.version)
|
12
|
-
end
|
13
|
-
|
14
|
-
should 'configure with a block' do
|
15
|
-
assert_nil Subtrigger.svn
|
16
|
-
output = Subtrigger.configure do |c|
|
17
|
-
c.svn = 'foo'
|
18
|
-
end
|
19
|
-
assert_equal('foo', Subtrigger.svn)
|
20
|
-
assert_equal(Subtrigger, output)
|
21
|
-
end
|
22
|
-
|
23
|
-
should 'Create new Repository object' do
|
24
|
-
Subtrigger::Repository.expects(:new).with('foo', '1')
|
25
|
-
Subtrigger.run('foo', '1')
|
26
|
-
end
|
27
|
-
|
28
|
-
should 'Run all triggers' do
|
29
|
-
Subtrigger::Repository.stubs(:new).returns('foo')
|
30
|
-
Subtrigger::Trigger.expects(:run).with('foo')
|
31
|
-
Subtrigger.run('foo', '1')
|
32
|
-
end
|
33
|
-
|
34
|
-
should 'create a new trigger' do
|
35
|
-
assert_equal(0, Subtrigger::Trigger.triggers.size)
|
36
|
-
Subtrigger.on(/foo/) { |m,r| }
|
37
|
-
assert_equal(1, Subtrigger::Trigger.triggers.size)
|
38
|
-
end
|
39
|
-
|
40
|
-
should 'chain creation of triggers' do
|
41
|
-
assert_equal(Subtrigger, Subtrigger.on(/foo/) { |m,r| })
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
data/test/test_trigger.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
|
-
class TestTrigger < Test::Unit::TestCase
|
4
|
-
context 'in a clean state' do
|
5
|
-
setup do
|
6
|
-
Subtrigger::Trigger.reset
|
7
|
-
end
|
8
|
-
|
9
|
-
should 'define a new trigger' do
|
10
|
-
Subtrigger::Trigger.define(/foo/) { |m, r| raise 'bar' }
|
11
|
-
assert_equal(1, Subtrigger::Trigger.triggers.size)
|
12
|
-
end
|
13
|
-
|
14
|
-
should 'apply all triggers' do
|
15
|
-
i = 0
|
16
|
-
Subtrigger::Trigger.define(/foo/) { |m,r| i += 1 }
|
17
|
-
Subtrigger::Trigger.define(/o+/) { |m,r| i += 1 }
|
18
|
-
Subtrigger::Trigger.run(stub(:message => 'foo bar'))
|
19
|
-
assert_equal(2, i)
|
20
|
-
end
|
21
|
-
|
22
|
-
should 'ignore unmatching triggers' do
|
23
|
-
i = 0
|
24
|
-
Subtrigger::Trigger.define(/foo/) { |m,r| i += 1 }
|
25
|
-
Subtrigger::Trigger.define(/x+/) { |m,r| i += 1 }
|
26
|
-
Subtrigger::Trigger.run(stub(:message => 'foo bar'))
|
27
|
-
assert_equal(1, i)
|
28
|
-
end
|
29
|
-
|
30
|
-
should 'empty list of triggers' do
|
31
|
-
Subtrigger::Trigger.define(/foo/) { |m,r| }
|
32
|
-
assert_equal(1, Subtrigger::Trigger.triggers.size)
|
33
|
-
Subtrigger::Trigger.reset
|
34
|
-
assert_equal(0, Subtrigger::Trigger.triggers.size)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|