git_breeze 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.
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+ require 'git_breeze/branch'
3
+
4
+ describe GitBreeze::Branch do
5
+ subject(:branch) { described_class }
6
+
7
+ def stub_branch(ref, exit_status = 0)
8
+ allow_message_expectations_on_nil
9
+ allow(branch).to receive(:`) { ref }
10
+ allow($?).to receive(:exitstatus) { exit_status }
11
+ end
12
+
13
+ describe '.current' do
14
+ it 'shells out to git, looking for the current HEAD' do
15
+ stub_branch('refs/heads/herpty_derp_de')
16
+ expect(branch).to receive('`').with('git symbolic-ref HEAD')
17
+ branch.current
18
+ end
19
+
20
+ it 'ensures in a Git repository when looking for HEAD exits with non-zero status' do
21
+ stub_branch('', 128)
22
+
23
+ expect(GitBreeze::Repository).to receive(:ensure_exists)
24
+ branch.current
25
+ end
26
+ end
27
+
28
+ describe '.story_number' do
29
+ context 'Current branch has a story number' do
30
+ it 'finds the story that starts with a hash' do
31
+ stub_branch('refs/heads/a_very_descriptive_name_#8675309')
32
+ expect(branch.story_number).to eq('8675309')
33
+ end
34
+
35
+ it 'finds the story without a leading hash' do
36
+ stub_branch('refs/heads/a_very_descriptive_name_1235309')
37
+ expect(branch.story_number).to eq('1235309')
38
+ end
39
+
40
+ it 'finds the story following a forward hash' do
41
+ stub_branch('refs/heads/alindeman/8675309_got_her_number')
42
+ expect(branch.story_number).to eq('8675309')
43
+ end
44
+
45
+ it 'finds the story in a branch with hyphens' do
46
+ stub_branch('refs/heads/stevenharman/got-her-number-8675309')
47
+ expect(branch.story_number).to eq('8675309')
48
+ end
49
+
50
+ it 'finds the story in a branch with a version number' do
51
+ stub_branch('refs/heads/stevenharman/v2.0-got-her-number-8675309')
52
+ expect(branch.story_number).to eq('8675309')
53
+ end
54
+ end
55
+
56
+ context 'The current branch has a number that is not a story' do
57
+ it 'finds no story' do
58
+ stub_branch('refs/heads/a_very_descriptive_name_with_some_a_version_number_v2.0')
59
+ expect(branch.story_number).to_not be
60
+ end
61
+ end
62
+
63
+ context 'The current branch does not have a story number' do
64
+ it 'finds no story' do
65
+ stub_branch('refs/heads/a_very_descriptive_name-without_a_#number')
66
+ expect(branch.story_number).to_not be
67
+ end
68
+ end
69
+
70
+ context 'Not on a branch (HEAD does not exist)' do
71
+ it 'finds no story' do
72
+ stub_branch('')
73
+ expect(branch.story_number).to_not be
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+ require 'git_breeze/commit_message'
3
+ require 'active_support/core_ext/string/strip'
4
+
5
+ describe GitBreeze::CommitMessage do
6
+ include CommitMessageHelper
7
+
8
+ subject(:commit_message) { described_class.new(file) }
9
+ let(:file) { 'COMMIT_EDITMSG' }
10
+
11
+ it 'requires path to the temporary commit message file' do
12
+ expect { GitBreeze::CommitMessage.new }.to raise_error ArgumentError
13
+ end
14
+
15
+ def stub_commit_message(story_text)
16
+ allow(File).to receive(:read).with(file) { example_commit_message(story_text) }
17
+ end
18
+
19
+ # describe '#keyword' do
20
+ # %w[fix Fixed FIXES Complete completed completes FINISH finished Finishes Deliver delivered DELIVERS].each do |keyword|
21
+ # it "detects the #{keyword} keyword" do
22
+ # stub_commit_message("Did the darn thing. [#{keyword}]")
23
+ # expect(commit_message.keyword).to eq(keyword)
24
+ # end
25
+ # end
26
+ #
27
+ # it 'does not find the keyword when it does not exist' do
28
+ # stub_commit_message('Did the darn thing. [Something]')
29
+ # expect(commit_message.keyword).to_not be
30
+ # end
31
+ # end
32
+
33
+ describe '#mentions_story?' do
34
+ context 'commit message contains the special Breeze story syntax' do
35
+ it 'allows just the number' do
36
+ stub_commit_message('[#8675309]')
37
+ expect(commit_message).to be_mentions_story('8675309')
38
+ end
39
+
40
+ it 'allows multiple numbers' do
41
+ stub_commit_message('[#99 #777 #8675309 #111222]')
42
+ expect(commit_message).to be_mentions_story('99')
43
+ expect(commit_message).to be_mentions_story('777')
44
+ expect(commit_message).to be_mentions_story('8675309')
45
+ expect(commit_message).to be_mentions_story('111222')
46
+ end
47
+
48
+ it 'allows state change before number' do
49
+ stub_commit_message('[Fixes #8675309]')
50
+ expect(commit_message).to be_mentions_story('8675309')
51
+ end
52
+
53
+ it 'allows state change after the number' do
54
+ stub_commit_message('[#8675309 Delivered]')
55
+ expect(commit_message).to be_mentions_story('8675309')
56
+ end
57
+
58
+ it 'allows surrounding text' do
59
+ stub_commit_message('derp de #herp [Fixes #8675309] de herp-ity derp')
60
+ expect(commit_message).to be_mentions_story('8675309')
61
+ end
62
+ end
63
+
64
+ context 'commit message doesn not contain the special Breeze story syntax' do
65
+ # it 'requires brackets' do
66
+ # stub_commit_message('#8675309')
67
+ # expect(commit_message).to_not be_mentions_story('8675309')
68
+ # end
69
+
70
+ it 'requires a pound sign' do
71
+ stub_commit_message('[8675309]')
72
+ expect(commit_message).to_not be_mentions_story('8675309')
73
+ end
74
+
75
+ it 'does not allow the bare number' do
76
+ stub_commit_message('8675309')
77
+ expect(commit_message).to_not be_mentions_story('8675309')
78
+ end
79
+
80
+ # it 'does not allow multiple state changes' do
81
+ # stub_commit_message('[Fixes Deploys #8675309]')
82
+ # expect(commit_message).to_not be_mentions_story('8675309')
83
+ # end
84
+
85
+ # it 'does not allow comments' do
86
+ # stub_commit_message('#[#8675309]')
87
+ # expect(commit_message).to_not be_mentions_story('8675309')
88
+ # end
89
+ end
90
+ end
91
+
92
+ describe '#append' do
93
+ let(:fake_file) { GitBreeze::FakeFile.new }
94
+ before do
95
+ allow(File).to receive(:open).and_yield(fake_file)
96
+ end
97
+ def stub_original_commit_message(message)
98
+ allow(File).to receive(:read) { message }
99
+ end
100
+
101
+ it 'handles no existing message' do
102
+ commit_message_text = <<-COMMIT_MESSAGE.strip_heredoc
103
+
104
+
105
+ #8675309
106
+ # some other comments
107
+ COMMIT_MESSAGE
108
+
109
+ stub_original_commit_message("\n\n# some other comments\n")
110
+ commit_message.append('[#8675309]')
111
+
112
+ expect(fake_file.content).to eq(commit_message_text)
113
+ end
114
+
115
+ it 'preserves existing messages' do
116
+ commit_message_text = <<-COMMIT_MESSAGE.strip_heredoc
117
+ A first line
118
+
119
+ With more here
120
+
121
+ #8675309
122
+ # other comments
123
+ COMMIT_MESSAGE
124
+
125
+ stub_original_commit_message("A first line\n\nWith more here\n# other comments\n")
126
+ commit_message.append('[#8675309]')
127
+
128
+ expect(fake_file.content).to eq(commit_message_text)
129
+ end
130
+
131
+ it 'preserves line breaks in comments' do
132
+ commit_message_text = <<-COMMIT_MESSAGE.strip_heredoc
133
+
134
+
135
+ #8675309
136
+ # comment #1
137
+ # comment B
138
+ # comment III
139
+ COMMIT_MESSAGE
140
+
141
+ stub_original_commit_message("# comment #1\n# comment B\n# comment III")
142
+ commit_message.append('[#8675309]')
143
+
144
+ expect(fake_file.content).to eq(commit_message_text)
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+ require 'git_breeze/hook'
3
+ require 'active_support/core_ext/string/strip'
4
+
5
+ describe GitBreeze::Hook do
6
+ subject(:hook) { described_class }
7
+ let(:root) { '/path/to/git/repo/toplevel' }
8
+ let(:hook_path) { File.join(root, '.git', 'hooks', 'prepare-commit-msg') }
9
+
10
+ describe '.init' do
11
+ before do
12
+ allow(GitBreeze::Repository).to receive(:root) { root }
13
+ allow(hook).to receive(:init_at)
14
+ end
15
+
16
+ it 'initializes to the root of the Git repository' do
17
+ hook.init
18
+ expect(hook).to have_received(:init_at).with(root)
19
+ end
20
+ end
21
+
22
+ describe '.init_at' do
23
+ let(:fake_file) { GitBreeze::FakeFile.new }
24
+ before do
25
+ allow(File).to receive(:open).and_yield(fake_file)
26
+ end
27
+
28
+ it 'writes the hook into the hooks directory' do
29
+ hook.init_at(root)
30
+ expect(File).to have_received(:open).with(hook_path, 'w')
31
+ end
32
+
33
+ it 'makes the hook executable' do
34
+ hook.init_at(root)
35
+ expect(fake_file.mode).to eq(0755)
36
+ end
37
+
38
+ it 'writes the hook code in the hook file' do
39
+ hook_code = <<-HOOK_CODE.strip_heredoc
40
+ #!/usr/bin/env bash
41
+
42
+ git-breeze prepare-commit-msg "$@"
43
+
44
+ HOOK_CODE
45
+
46
+ hook.init_at(root)
47
+ expect(fake_file.content).to eq(hook_code)
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+ require 'git_breeze/prepare_commit_message'
3
+
4
+ describe GitBreeze::PrepareCommitMessage do
5
+ subject(:prepare_commit_message) { GitBreeze::PrepareCommitMessage }
6
+
7
+ describe '.run' do
8
+ let(:hook) { double('PrepareCommitMessage') }
9
+ before do
10
+ allow(prepare_commit_message).to receive(:new) { hook }
11
+ end
12
+
13
+ it 'runs the hook' do
14
+ expect(hook).to receive(:run)
15
+ prepare_commit_message.run('FILE1', 'hook_source', 'sha1234')
16
+ end
17
+ end
18
+
19
+ describe '.new' do
20
+
21
+ it 'requires the name of the commit message file' do
22
+ expect { prepare_commit_message.new }.to raise_error(ArgumentError)
23
+ end
24
+
25
+ it 'remembers the name of the commit message file' do
26
+ expect(prepare_commit_message.new('FILE1').file).to eq('FILE1')
27
+ end
28
+
29
+ it 'optionally accepts a message source' do
30
+ expect(hook = prepare_commit_message.new('FILE1', 'merge').source).to eq('merge')
31
+ end
32
+
33
+ it 'optionally accepts the SHA-1 of a commit' do
34
+ expect(hook = prepare_commit_message.new('FILE1', 'commit', 'abc1234').commit_sha).to eq('abc1234')
35
+ end
36
+ end
37
+
38
+ describe '#run' do
39
+ let(:hook) { GitBreeze::PrepareCommitMessage.new('FILE1') }
40
+ let(:commit_message) { double('CommitMessage', :append => nil) }
41
+
42
+ before do
43
+ allow(GitBreeze::Branch).to receive(:story_number) { story }
44
+ allow(GitBreeze::CommitMessage).to receive(:new) { commit_message }
45
+ end
46
+
47
+ context 'with an existing commit (via `-c`, `-C`, or `--amend` options)' do
48
+ let(:hook) { described_class.new('FILE2', 'commit', '60a086f3') }
49
+
50
+ it 'exits with status code 0' do
51
+ expect { hook.run }.to succeed
52
+ end
53
+ end
54
+
55
+ context 'branch name without a Breeze story number' do
56
+ let(:story) { nil }
57
+
58
+ it 'exits without updating the commit message' do
59
+ expect { hook.run }.to succeed
60
+ expect(commit_message).to_not have_received(:append)
61
+ end
62
+ end
63
+
64
+ context 'branch name with a Breeze story number' do
65
+ let(:story) { '8675309' }
66
+ before do
67
+ allow(commit_message).to receive(:mentions_story?) { false }
68
+ allow(commit_message).to receive(:keyword) { nil }
69
+ end
70
+
71
+ it 'appends the number to the commit message' do
72
+ hook.run
73
+ expect(commit_message).to have_received(:append).with('#8675309')
74
+ end
75
+
76
+ context 'keyword mentioned in the commit message' do
77
+ before do
78
+ allow(commit_message).to receive(:keyword) { 'Delivers' }
79
+ end
80
+
81
+ it 'appends the keyword and the story number' do
82
+ hook.run
83
+ expect(commit_message).to have_received(:append).with('Delivers #8675309')
84
+ end
85
+ end
86
+
87
+ context 'number already mentioned in the commit message' do
88
+ before do
89
+ allow(commit_message).to receive(:mentions_story?).with('8675309') { true }
90
+ end
91
+
92
+ it 'exits without updating the commit message' do
93
+ expect { hook.run }.to succeed
94
+ expect(commit_message).to_not have_received(:append)
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'git_breeze/repository'
3
+
4
+ describe GitBreeze::Repository do
5
+ subject(:repository) { described_class }
6
+ let(:git_command) { 'git rev-parse --show-toplevel' }
7
+ before do
8
+ allow_message_expectations_on_nil
9
+ allow(repository).to receive(:`).with(git_command) { "/path/to/git/repo/root\n" }
10
+ end
11
+
12
+ describe '.root' do
13
+
14
+ it 'gets the path to the top-level directory of the local Repository' do
15
+ allow($?).to receive(:exitstatus) { 0 }
16
+ expect(repository.root).to eq('/path/to/git/repo/root')
17
+ end
18
+
19
+ it 'aborts when not in a git repository' do
20
+ allow($?).to receive(:exitstatus) { 128 }
21
+ expect { repository.root }.to_not succeed
22
+ end
23
+ end
24
+
25
+ describe '.ensure_exists' do
26
+ it 'aborts when not in a git repository' do
27
+ allow($?).to receive(:exitstatus) { 128 }
28
+ expect { repository.root }.to_not succeed
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ require 'git_breeze/runner'
3
+
4
+ describe GitBreeze::Runner do
5
+ subject(:runner) { described_class }
6
+ let(:args) { ['a_file', 'the_source', 'sha1234'] }
7
+
8
+ describe '.execute' do
9
+ include OutputHelper
10
+
11
+ before do
12
+ allow(runner).to receive(:prepare_commit_msg) { true }
13
+ end
14
+
15
+ it 'runs the hook, passing the args' do
16
+ expect(runner).to receive(:prepare_commit_msg).with(*args) { true }
17
+ runner.execute('prepare-commit-msg', *args)
18
+ end
19
+
20
+ it 'does not run hooks we do not know about' do
21
+ errors = capture_stderr do
22
+ expect { runner.execute('non-existent-hook', *args) }.to_not succeed
23
+ end
24
+ expect(errors.chomp).to eq("[git_tracker] command: 'non-existent-hook' does not exist.")
25
+ end
26
+ end
27
+
28
+ describe '.prepare_commit_msg' do
29
+ it 'runs the hook, passing the args' do
30
+ expect(GitBreeze::PrepareCommitMessage).to receive(:run).with(*args) { true }
31
+ runner.prepare_commit_msg(*args)
32
+ end
33
+ end
34
+
35
+ describe '.init' do
36
+ it 'tells the hook to initialize itself' do
37
+ expect(GitBreeze::Hook).to receive(:init)
38
+ runner.init
39
+ end
40
+ end
41
+
42
+ it '.help reports that it was run' do
43
+ expect(runner).to receive(:puts).with(/git-breeze #{GitBreeze::VERSION} is installed\./)
44
+ runner.execute('help')
45
+ end
46
+
47
+ end
@@ -0,0 +1,88 @@
1
+ require 'git_breeze/standalone'
2
+
3
+ describe GitBreeze::Standalone do
4
+
5
+ describe '#save' do
6
+ before do
7
+ File.delete 'git-breeze' if File.exists? 'git-breeze'
8
+ end
9
+
10
+ after do
11
+ File.delete 'git-breeze' if File.exists? 'git-breeze'
12
+ end
13
+
14
+ it 'saves to the named file' do
15
+ described_class.save('git-breeze')
16
+ expect(File.size('./git-breeze')).to be > 100
17
+ end
18
+
19
+ it 'marks the binary as executable' do
20
+ described_class.save('git-breeze')
21
+ expect(File).to be_executable('./git-breeze')
22
+ end
23
+
24
+ end
25
+
26
+ describe '#build' do
27
+ subject(:standalone_script) { described_class.build(io).string }
28
+ let(:io) { StringIO.new }
29
+
30
+ it 'declares a shebang' do
31
+ expect(standalone_script).to match(/#!.+/)
32
+ end
33
+
34
+ it 'includes generated code notice' do
35
+ expect(standalone_script).to include('This file is generated')
36
+ end
37
+
38
+ it 'inlines the code' do
39
+ expect(standalone_script).to include('Hook')
40
+ expect(standalone_script).to include('Repository')
41
+ expect(standalone_script).to include('PrepareCommitMessage')
42
+ expect(standalone_script).to include('Runner')
43
+ expect(standalone_script).to include('Branch')
44
+ expect(standalone_script).to include('CommitMessage')
45
+ expect(standalone_script).to include('VERSION')
46
+ end
47
+
48
+ it 'inlines the message HEREDOC' do
49
+ expect(standalone_script).to include('#{preamble.strip}')
50
+ end
51
+
52
+ it 'inlines the shebang for the hook' do
53
+ expect(standalone_script).to include('#!/usr/bin/env bash')
54
+ end
55
+
56
+ it 'does not inline the standalone code' do
57
+ expect(standalone_script).to_not include('module Standalone')
58
+ end
59
+
60
+ it 'includes the call to execute the hook' do
61
+ expect(standalone_script).to include('GitBreeze::Runner.execute(*ARGV)')
62
+ end
63
+
64
+ it 'excludes requiring git_breeze code' do
65
+ expect(standalone_script).to_not match(/^require\s+["']git_breeze/)
66
+ end
67
+
68
+ end
69
+
70
+ describe '#ruby_executable' do
71
+ subject(:standalone) { described_class }
72
+ before do
73
+ allow(RbConfig::CONFIG).to receive(:[]).with('bindir') { '/some/other/bin' }
74
+ allow(RbConfig::CONFIG).to receive(:[]).with('ruby_install_name') { 'ruby' }
75
+ end
76
+
77
+ it 'uses user-level ruby binary when it is executable' do
78
+ allow(File).to receive(:executable?).with('/usr/bin/ruby') { true }
79
+ expect(standalone.ruby_executable).to eq('/usr/bin/ruby')
80
+ end
81
+
82
+ it 'uses rbconfig ruby when user-level ruby binary not executable' do
83
+ allow(File).to receive(:executable?).with('/usr/bin/ruby') { false }
84
+ expect(standalone.ruby_executable).to eq('/some/other/bin/ruby')
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,11 @@
1
+ if (RUBY_VERSION >= '1.9') && ENV['CODECLIMATE_REPO_TOKEN']
2
+ require 'codeclimate-test-reporter'
3
+ CodeClimate::TestReporter.start
4
+ end
5
+
6
+ Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each {|f| require f }
7
+
8
+ RSpec.configure do |config|
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+ end
@@ -0,0 +1,20 @@
1
+ module CommitMessageHelper
2
+
3
+ def example_commit_message(pattern_to_match)
4
+ return <<-EXAMPLE
5
+ Got Jenny's number, gonna' make her mine!
6
+
7
+ #{pattern_to_match}
8
+ # Please enter the commit message for your changes. Lines starting
9
+ # with '#' will be ignored, and an empty message aborts the commit.
10
+ # On branch get_jennys_number_#8675309
11
+ # Changes to be committed:
12
+ # (use "git reset HEAD <file>..." to unstage)
13
+ #
14
+ # new file: fake_file.rb
15
+ #
16
+
17
+ EXAMPLE
18
+ end
19
+ end
20
+
@@ -0,0 +1,13 @@
1
+ module GitTracker
2
+ class FakeFile
3
+ attr_reader :content, :mode
4
+
5
+ def write(content)
6
+ @content = content
7
+ end
8
+
9
+ def chmod(mode_int)
10
+ @mode = mode_int
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ require 'rspec/expectations'
2
+
3
+ RSpec::Matchers.define :succeed do
4
+ actual = nil
5
+
6
+ match do |block|
7
+ begin
8
+ block.call
9
+ rescue SystemExit => e
10
+ actual = e.status
11
+ end
12
+ actual and actual == successful_exit_code
13
+ end
14
+
15
+ failure_message do |block|
16
+ "expected block to call exit(#{successful_exit_code}) but exit" +
17
+ (actual.nil? ? ' not called' : "(#{actual}) was called")
18
+ end
19
+
20
+ failure_message_when_negated do |block|
21
+ "expected block not to call exit(#{successful_exit_code})"
22
+ end
23
+
24
+ description do
25
+ "expect block to call exit(#{successful_exit_code})"
26
+ end
27
+
28
+ def successful_exit_code
29
+ 0
30
+ end
31
+
32
+ def supports_block_expectations?
33
+ true
34
+ end
35
+
36
+ end
37
+
@@ -0,0 +1,12 @@
1
+ require 'stringio'
2
+
3
+ module OutputHelper
4
+ def capture_stderr
5
+ old_out, new_out = $stderr, StringIO.new
6
+ $stderr = new_out
7
+ yield
8
+ ensure
9
+ $stderr = old_out
10
+ return new_out.string
11
+ end
12
+ end