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 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.
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ require 'raincoat/hook'
2
+ require 'raincoat/script_writer'
3
+ require 'raincoat/installer'
4
+ require 'raincoat/diff_utils'
@@ -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
+