raincoat 0.1
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/LICENSE +19 -0
- data/README.markdown +66 -0
- data/Rakefile +73 -0
- data/bin/raincoat +12 -0
- data/lib/raincoat.rb +4 -0
- data/lib/raincoat/diff_utils.rb +33 -0
- data/lib/raincoat/hook.rb +60 -0
- data/lib/raincoat/installer.rb +32 -0
- data/lib/raincoat/script_writer.rb +69 -0
- metadata +63 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Ryan Burrows
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Raincoat
|
2
|
+
|
3
|
+
http://github.com/rhburrows/raincoat
|
4
|
+
|
5
|
+
## Intro
|
6
|
+
|
7
|
+
Git hooks are scripts that git runs before and after it call some
|
8
|
+
commands. They can be specified by placing executable files in the
|
9
|
+
`.git/hooks` directory and naming them according to a certain
|
10
|
+
convention. However, the `.git/hooks` directory is not distributed
|
11
|
+
with the git repository, so any hooks that you install must be
|
12
|
+
reinstalled every time you or anyone else clones the project.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Raincoat can be installed through rubygems.
|
17
|
+
|
18
|
+
gem install raincoat
|
19
|
+
|
20
|
+
If you wish to run the tests, or build the documentation you will also
|
21
|
+
need to install rspec, cucumber, and yard
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
The first step is to initialize the raincoat hooks in your
|
26
|
+
project. This can be done by running the following command from the
|
27
|
+
root directory of your project.
|
28
|
+
|
29
|
+
raincoat install
|
30
|
+
|
31
|
+
At this time existing git-hooks that you have may be wiped out so be
|
32
|
+
sure to back up any hooks that you want to save.
|
33
|
+
|
34
|
+
Raincoat works by looking in your script directory for the directory
|
35
|
+
with the same name as the hook being executed, and then it runs each
|
36
|
+
command in there. For example, the when the `pre-commit` hook is
|
37
|
+
executed it will look in `<script_dir>/pre-commit` and run each script
|
38
|
+
in there.
|
39
|
+
|
40
|
+
By default the script directory is set to `script` but a different
|
41
|
+
script directory can be specified by passing it to the
|
42
|
+
install command:
|
43
|
+
|
44
|
+
raincoat install hooks
|
45
|
+
|
46
|
+
This would create a hooks directory where all of the hook scripts
|
47
|
+
could be placed.
|
48
|
+
|
49
|
+
## Anatomy of a Raincoat Script
|
50
|
+
|
51
|
+
Raincoat expects each of the scripts within the various hook
|
52
|
+
directories to follow two conventions.
|
53
|
+
|
54
|
+
1. Define a class with a name corresponding to the file name
|
55
|
+
i.e. `email_changes.rb` should contain a class called `EmailChanges`
|
56
|
+
2. The defined call should respond to the `call` method with one
|
57
|
+
parameter
|
58
|
+
|
59
|
+
The parameter that is passed to the `call` method is a string
|
60
|
+
representation of the change that is being made in git. For example in
|
61
|
+
the case of `pre-commit` it is the difference between the files that
|
62
|
+
are currently staged and the `HEAD` commit.
|
63
|
+
|
64
|
+
## TODO
|
65
|
+
|
66
|
+
* Add support for additional git-hooks
|
data/Rakefile
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
spec = Gem::Specification.new do |s|
|
4
|
+
s.name = 'raincoat'
|
5
|
+
s.version = '0.1'
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.summary = 'Easily define git-hooks that can be passed with your project'
|
8
|
+
s.description = s.summary
|
9
|
+
s.author = 'Ryan Burrows'
|
10
|
+
s.email = 'rhburrows@gmail.com'
|
11
|
+
s.homepage = 'http://github.com/rhburrows/raincoat'
|
12
|
+
s.require_path = 'lib'
|
13
|
+
s.files = %w[LICENSE README.markdown Rakefile] + Dir.glob("{bin,lib}/**/*")
|
14
|
+
s.bindir = 'bin'
|
15
|
+
s.executables = ['raincoat']
|
16
|
+
end
|
17
|
+
|
18
|
+
spec_file = "#{spec.name}.gemspec"
|
19
|
+
desc "Create #{spec_file}"
|
20
|
+
file spec_file => "Rakefile" do
|
21
|
+
File.open(spec_file, "w") do |file|
|
22
|
+
file.puts spec.to_ruby
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'rake/gempackagetask'
|
28
|
+
rescue LoadError
|
29
|
+
task(:gem){ $stderr.puts "`gem install rake` to package gems" }
|
30
|
+
else
|
31
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
32
|
+
pkg.gem_spec = spec
|
33
|
+
end
|
34
|
+
task :gem => spec_file
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "install the gem locally"
|
38
|
+
task :install => :package do
|
39
|
+
`gem install pkg/#{spec.name}-#{spec.version}`
|
40
|
+
end
|
41
|
+
|
42
|
+
begin
|
43
|
+
require 'spec/rake/spectask'
|
44
|
+
Spec::Rake::SpecTask.new do |t|
|
45
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
46
|
+
end
|
47
|
+
rescue LoadError
|
48
|
+
task(:spec){ $stderr.puts "`gem install rspec` to run specs." }
|
49
|
+
end
|
50
|
+
|
51
|
+
task :default => [:spec, :features]
|
52
|
+
|
53
|
+
begin
|
54
|
+
require 'cucumber'
|
55
|
+
require 'cucumber/rake/task'
|
56
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
57
|
+
t.cucumber_opts = "features --format pretty"
|
58
|
+
end
|
59
|
+
rescue LoadError
|
60
|
+
task(:features){ $stderr.puts "`gem install cucumber` to run features." }
|
61
|
+
end
|
62
|
+
|
63
|
+
begin
|
64
|
+
require 'yard'
|
65
|
+
YARD::Rake::YardocTask.new do |t|
|
66
|
+
t.files = ['lib/**/*.rb']
|
67
|
+
t.options = [ '--protected',
|
68
|
+
'--files', 'History.txt',
|
69
|
+
'--title', 'raincoat' ]
|
70
|
+
end
|
71
|
+
rescue LoadError
|
72
|
+
task(:yard){ $stderr.puts "`gem install yard` to build documentation." }
|
73
|
+
end
|
data/bin/raincoat
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
raincoat_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
$LOAD_PATH.unshift(raincoat_dir) unless $LOAD_PATH.include?(raincoat_dir)
|
4
|
+
require 'raincoat'
|
5
|
+
|
6
|
+
unless ARGV[0] == 'install'
|
7
|
+
$stderr.puts "raincoat only supports installation. Try `raincoat install <script_dir>`"
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
|
11
|
+
script_dir = ARGV[1] || "script"
|
12
|
+
Raincoat::Installer.new(script_dir).install
|
data/lib/raincoat.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Raincoat
|
2
|
+
# A set of utility methods that shell out to git to find changes that have
|
3
|
+
# been made to the code. These changes can then be passed to the various
|
4
|
+
# hook scripts that have been set up
|
5
|
+
#
|
6
|
+
# @author Ryan Burrows
|
7
|
+
class DiffUtils
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Returns the diff between all staged changes and the current head
|
11
|
+
#
|
12
|
+
# @return [String] a string representation of the difference between
|
13
|
+
# the staged changes and the head
|
14
|
+
def precommit_diff
|
15
|
+
execute "git diff --cached"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the diff between the current head and the previous head
|
19
|
+
#
|
20
|
+
# @return [String] a string representation of the difference between
|
21
|
+
# the current head and the previous head
|
22
|
+
def postcommit_diff
|
23
|
+
execute "git diff HEAD^"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def execute(cmd)
|
29
|
+
`#{cmd}`
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Raincoat
|
2
|
+
# The superclass for the various hooks that are installed to be run by git.
|
3
|
+
# subclasses of Hook should override git_diff to generate the correct diff
|
4
|
+
# string so that it can be passed to the hook scripts.
|
5
|
+
#
|
6
|
+
# @author Ryan Burrows
|
7
|
+
class Hook
|
8
|
+
attr_reader :script_dir
|
9
|
+
|
10
|
+
# Create a new hook object that will execute the scripts located in
|
11
|
+
# script_dir
|
12
|
+
#
|
13
|
+
# @param [String] script_dir the path of the directory of scripts that
|
14
|
+
# this hook executes
|
15
|
+
def initialize(script_dir)
|
16
|
+
@script_dir = script_dir
|
17
|
+
end
|
18
|
+
|
19
|
+
# This is called when the hook is executed by git's callbacks. It loads
|
20
|
+
# each file in the script_dir and creates an instance of the defined class
|
21
|
+
# call is then called on the instance being passed the result of git_diff.
|
22
|
+
#
|
23
|
+
# @return [Integer] the sum of the exit statuses of all executed scripts.
|
24
|
+
# This number becomes the overall exit status of the hook.
|
25
|
+
def run
|
26
|
+
diff = git_diff.freeze
|
27
|
+
scripts.inject(0) do |result, script|
|
28
|
+
result + script.call(diff)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# This loads all of the scripts in the script dir and instantiates the
|
33
|
+
# objects.
|
34
|
+
#
|
35
|
+
# @return [Array] the list of newly created objects
|
36
|
+
def scripts
|
37
|
+
Dir.glob(File.join(script_dir, "*.rb")).inject([]) do |a, e|
|
38
|
+
Kernel.load(e)
|
39
|
+
a + [initialize_script(e)]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Should be overridden by subclass to express the diff between the two
|
44
|
+
# git states that are being transitioned between.
|
45
|
+
def git_diff
|
46
|
+
""
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def initialize_script(script_name)
|
52
|
+
Object.const_get(class_name_for(script_name)).new
|
53
|
+
end
|
54
|
+
|
55
|
+
# Just like ActiveSupport's #classify
|
56
|
+
def class_name_for(file_name)
|
57
|
+
file_name.split(/\//).last.sub(/\.rb$/, '').split(/_/).map{ |s| s.capitalize }.join('')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Raincoat
|
2
|
+
# Installs the raincoat hooks into the .git directory of the current
|
3
|
+
# project.
|
4
|
+
#
|
5
|
+
# @author Ryan Burrows
|
6
|
+
class Installer
|
7
|
+
# The list of currently supported hooks
|
8
|
+
HOOKS = [ "pre-commit", "post-commit" ]
|
9
|
+
|
10
|
+
# Create an installer that watches scripts contained within script_dir
|
11
|
+
#
|
12
|
+
# @param [String] script_dir the path to the directory where the various
|
13
|
+
# scripts that raincoat will run are put.
|
14
|
+
def initialize(script_dir)
|
15
|
+
@script_dir = script_dir
|
16
|
+
end
|
17
|
+
|
18
|
+
# Install raincoat hooks in the current project for each of the supported
|
19
|
+
# git-hook types. This will create the corresponding script directory if
|
20
|
+
# it doesn't exist
|
21
|
+
def install
|
22
|
+
unless File.directory?(@script_dir)
|
23
|
+
FileUtils.mkdir_p(@script_dir)
|
24
|
+
end
|
25
|
+
|
26
|
+
writer = ScriptWriter.new(@script_dir)
|
27
|
+
HOOKS.each do |hook|
|
28
|
+
writer.write(hook)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Raincoat
|
4
|
+
# This class generates and writes out the actual git-hook files into the
|
5
|
+
# project's .git directory.
|
6
|
+
#
|
7
|
+
# @author Ryan Burrows
|
8
|
+
class ScriptWriter
|
9
|
+
|
10
|
+
# The directory where git stores it's hooks
|
11
|
+
GIT_HOOK_DIR = File.join(".git","hooks")
|
12
|
+
|
13
|
+
# Create a new ScriptWriter that creates hooks that executes the scripts
|
14
|
+
# in script_dir
|
15
|
+
#
|
16
|
+
# @param [String] script_dir the path to the directory where the various
|
17
|
+
# scripts exist
|
18
|
+
def initialize(script_dir)
|
19
|
+
@script_dir = script_dir
|
20
|
+
end
|
21
|
+
|
22
|
+
# Write a new hook to the git directory called hook_name. Also, if there
|
23
|
+
# isn't a corresponding 'hook_name' directory in the script_dir it will
|
24
|
+
# be created.
|
25
|
+
#
|
26
|
+
# @param [String] hook_name the name of the git-hook to write this file for
|
27
|
+
def write(hook_name)
|
28
|
+
init_script_directory(File.join(@script_dir, hook_name))
|
29
|
+
path = File.join(GIT_HOOK_DIR, hook_name)
|
30
|
+
File.open(path, "w") do |f|
|
31
|
+
f.puts(build_script(hook_name))
|
32
|
+
end
|
33
|
+
FileUtils.chmod 0777, path
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generate the script that is going to be written in the git-hook file.
|
37
|
+
#
|
38
|
+
# @param [String] hook_name the name of the git-hook to build a script for
|
39
|
+
def build_script(hook_name)
|
40
|
+
hook_type = hook_name.sub(/-/, '')
|
41
|
+
script_dir = File.join(@script_dir, hook_name)
|
42
|
+
ERB.new(DEFAULT_TEMPLATE).result(binding)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def init_script_directory(dir)
|
48
|
+
unless File.directory? dir
|
49
|
+
FileUtils.mkdir_p dir
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
DEFAULT_TEMPLATE = <<TEMPLATE
|
54
|
+
#!/usr/bin/env ruby
|
55
|
+
require 'rubygems'
|
56
|
+
require 'raincoat'
|
57
|
+
|
58
|
+
class ActiveHook < Raincoat::Hook
|
59
|
+
def git_diff
|
60
|
+
Raincoat::DiffUtils.<%= hook_type %>_diff
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "RUNNING!"
|
65
|
+
exit(ActiveHook.new("<%= script_dir %>").run)
|
66
|
+
|
67
|
+
TEMPLATE
|
68
|
+
end
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: raincoat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Burrows
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-19 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Easily define git-hooks that can be passed with your project
|
17
|
+
email: rhburrows@gmail.com
|
18
|
+
executables:
|
19
|
+
- raincoat
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- LICENSE
|
26
|
+
- README.markdown
|
27
|
+
- Rakefile
|
28
|
+
- bin/raincoat
|
29
|
+
- lib/raincoat/diff_utils.rb
|
30
|
+
- lib/raincoat/script_writer.rb
|
31
|
+
- lib/raincoat/installer.rb
|
32
|
+
- lib/raincoat/hook.rb
|
33
|
+
- lib/raincoat.rb
|
34
|
+
has_rdoc: true
|
35
|
+
homepage: http://github.com/rhburrows/raincoat
|
36
|
+
licenses: []
|
37
|
+
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
requirements: []
|
56
|
+
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.3.5
|
59
|
+
signing_key:
|
60
|
+
specification_version: 3
|
61
|
+
summary: Easily define git-hooks that can be passed with your project
|
62
|
+
test_files: []
|
63
|
+
|