raincoat 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+