Skip to content

Commit 3413e45

Browse files
committed
Rework commit message mechanics
We can rely on Pathname to do some of the heavy lifting, while also hiding some implementation details (KEYWORD_REGEX), and optimizing some of our regex usage by using #match? when we don't need the MatchData object that would be created by a `#~`. We also remove our dependence on the FakeFile object, trading off that explicit isolation for a more pliable implementation. That is, our test setup doesn't rely on stubbing out Stdlib file access. Instead we set up some fixures via temp dirs and then read/write actual files.
1 parent 726d615 commit 3413e45

File tree

6 files changed

+84
-77
lines changed

6 files changed

+84
-77
lines changed

lib/git_tracker/commit_message.rb

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,36 @@
11
module GitTracker
22
class CommitMessage
33
def initialize(file)
4-
@file = file
5-
@message = File.read(@file)
6-
end
7-
8-
def mentions_story?(number)
9-
@message =~ /^(?!#).*\[(\w+\s)?(#\d+\s)*##{number}(\s#\d+)*(\s\w+)?\]/i
10-
end
11-
12-
def keyword
13-
@message =~ /\[(fix|fixes|fixed|complete|completes|completed|finish|finishes|finished|deliver|delivers|delivered)\]/io
14-
$1
4+
@file = Pathname(file)
155
end
166

177
def append(text)
18-
body, postscript = parse(@message)
8+
body, postscript = parse(message)
199
new_message = format_message(body, text, postscript)
20-
File.open(@file, "w") do |f|
10+
11+
file.open("w") do |f|
2112
f.write(new_message)
2213
end
14+
2315
new_message
2416
end
2517

26-
private
18+
def keyword
19+
matches = message.match(KEYWORD_REGEX) || {}
20+
matches[:keyword]
21+
end
2722

28-
def parse(message)
29-
lines = message.split($/)
30-
body = lines.take_while { |line| !line.start_with?("#") }
31-
postscript = lines.slice(body.length..-1)
32-
[body.join("\n"), postscript.join("\n")]
23+
def mentions_story?(number)
24+
/^(?!#).*\[(\w+\s)?(#\d+\s)*##{number}(\s#\d+)*(\s\w+)?\]/i.match?(message)
3325
end
3426

27+
private
28+
29+
KEYWORD_REGEX = /\[(?<keyword>fix|fixes|fixed|complete|completes|completed|finish|finishes|finished|deliver|delivers|delivered)\]/io.freeze
30+
private_constant :KEYWORD_REGEX
31+
32+
attr_reader :file
33+
3534
def format_message(preamble, text, postscript)
3635
<<~MESSAGE
3736
#{preamble.strip}
@@ -40,5 +39,16 @@ def format_message(preamble, text, postscript)
4039
#{postscript}
4140
MESSAGE
4241
end
42+
43+
def message
44+
@message ||= file.read.freeze
45+
end
46+
47+
def parse(raw_message)
48+
lines = raw_message.split($/)
49+
body = lines.take_while { |line| !line.start_with?("#") }
50+
postscript = lines.slice(body.length..-1)
51+
[body.join("\n"), postscript.join("\n")]
52+
end
4353
end
4454
end

spec/git_tracker/commit_message_spec.rb

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,101 +3,98 @@
33
RSpec.describe GitTracker::CommitMessage do
44
include CommitMessageHelper
55

6-
subject(:commit_message) { described_class.new(file) }
7-
let(:file) { "COMMIT_EDITMSG" }
6+
subject(:commit_message) { described_class.new(commit_editmsg_file.to_path) }
7+
let(:commit_editmsg_file) { Pathname(@git_dir).join("COMMIT_EDITMSG") }
8+
9+
around do |example|
10+
Dir.mktmpdir do |dir|
11+
@git_dir = dir
12+
example.call
13+
remove_instance_variable(:@git_dir)
14+
end
15+
end
816

917
it "requires path to the temporary commit message file" do
1018
expect { GitTracker::CommitMessage.new }.to raise_error ArgumentError
1119
end
1220

13-
def stub_commit_message(story_text)
14-
allow(File).to receive(:read).with(file) { example_commit_message(story_text) }
15-
end
16-
1721
describe "#keyword" do
1822
%w[fix Fixed FIXES Complete completed completes FINISH finished Finishes Deliver delivered DELIVERS].each do |keyword|
1923
it "detects the #{keyword} keyword" do
20-
stub_commit_message("Did the darn thing. [#{keyword}]")
24+
setup_commit_editmsg_file("Did the darn thing. [#{keyword}]")
2125
expect(commit_message.keyword).to eq(keyword)
2226
end
2327
end
2428

2529
it "does not find the keyword when it does not exist" do
26-
stub_commit_message("Did the darn thing. [Something]")
30+
setup_commit_editmsg_file("Did the darn thing. [Something]")
2731
expect(commit_message.keyword).to_not be
2832
end
2933
end
3034

3135
describe "#mentions_story?" do
3236
context "commit message contains the special Pivotal Tracker story syntax" do
3337
it "allows just the number" do
34-
stub_commit_message("[#8675309]")
38+
setup_commit_editmsg_file("[#8675309]")
3539
expect(commit_message).to be_mentions_story("8675309")
3640
end
3741

3842
it "allows multiple numbers" do
39-
stub_commit_message("[#99 #777 #8675309 #111222]")
40-
expect(commit_message).to be_mentions_story("99")
41-
expect(commit_message).to be_mentions_story("777")
42-
expect(commit_message).to be_mentions_story("8675309")
43-
expect(commit_message).to be_mentions_story("111222")
43+
setup_commit_editmsg_file("[#99 #777 #8675308 #111222]")
44+
45+
aggregate_failures do
46+
expect(commit_message).to be_mentions_story("99")
47+
expect(commit_message).to be_mentions_story("777")
48+
expect(commit_message).to be_mentions_story("8675308")
49+
expect(commit_message).to be_mentions_story("111222")
50+
end
4451
end
4552

4653
it "allows state change before number" do
47-
stub_commit_message("[Fixes #8675309]")
48-
expect(commit_message).to be_mentions_story("8675309")
54+
setup_commit_editmsg_file("[Fixes #8675307]")
55+
expect(commit_message).to be_mentions_story("8675307")
4956
end
5057

5158
it "allows state change after the number" do
52-
stub_commit_message("[#8675309 Delivered]")
53-
expect(commit_message).to be_mentions_story("8675309")
59+
setup_commit_editmsg_file("[#8675306 Delivered]")
60+
expect(commit_message).to be_mentions_story("8675306")
5461
end
5562

5663
it "allows surrounding text" do
57-
stub_commit_message("derp de #herp [Fixes #8675309] de herp-ity derp")
58-
expect(commit_message).to be_mentions_story("8675309")
64+
setup_commit_editmsg_file("derp de #herp [Fixes #8675305] de herp-ity derp")
65+
expect(commit_message).to be_mentions_story("8675305")
5966
end
6067
end
6168

6269
context "commit message doesn not contain the special Pivotal Tracker story syntax" do
6370
it "requires brackets" do
64-
stub_commit_message("#8675309")
71+
setup_commit_editmsg_file("#8675309")
6572
expect(commit_message).to_not be_mentions_story("8675309")
6673
end
6774

6875
it "requires a pound sign" do
69-
stub_commit_message("[8675309]")
76+
setup_commit_editmsg_file("[8675309]")
7077
expect(commit_message).to_not be_mentions_story("8675309")
7178
end
7279

7380
it "does not allow the bare number" do
74-
stub_commit_message("8675309")
81+
setup_commit_editmsg_file("8675309")
7582
expect(commit_message).to_not be_mentions_story("8675309")
7683
end
7784

7885
it "does not allow multiple state changes" do
79-
stub_commit_message("[Fixes Deploys #8675309]")
86+
setup_commit_editmsg_file("[Fixes Deploys #8675309]")
8087
expect(commit_message).to_not be_mentions_story("8675309")
8188
end
8289

8390
it "does not allow comments" do
84-
stub_commit_message("#[#8675309]")
91+
setup_commit_editmsg_file("#[#8675309]")
8592
expect(commit_message).to_not be_mentions_story("8675309")
8693
end
8794
end
8895
end
8996

9097
describe "#append" do
91-
let(:fake_file) { GitTracker::FakeFile.new }
92-
93-
before do
94-
allow(File).to receive(:open).and_yield(fake_file)
95-
end
96-
97-
def stub_original_commit_message(message)
98-
allow(File).to receive(:read) { message }
99-
end
100-
10198
it "handles no existing message" do
10299
commit_message_text = <<~COMMIT_MESSAGE
103100
@@ -106,10 +103,10 @@ def stub_original_commit_message(message)
106103
# some other comments
107104
COMMIT_MESSAGE
108105

109-
stub_original_commit_message("\n\n# some other comments\n")
106+
write_commit_editmsg_file("\n\n# some other comments\n")
110107
commit_message.append("[#8675309]")
111108

112-
expect(fake_file.content).to eq(commit_message_text)
109+
expect(commit_editmsg_file.read).to eq(commit_message_text)
113110
end
114111

115112
it "preserves existing messages" do
@@ -122,10 +119,10 @@ def stub_original_commit_message(message)
122119
# other comments
123120
COMMIT_MESSAGE
124121

125-
stub_original_commit_message("A first line\n\nWith more here\n# other comments\n")
122+
write_commit_editmsg_file("A first line\n\nWith more here\n# other comments\n")
126123
commit_message.append("[#8675309]")
127124

128-
expect(fake_file.content).to eq(commit_message_text)
125+
expect(commit_editmsg_file.read).to eq(commit_message_text)
129126
end
130127

131128
it "preserves line breaks in comments" do
@@ -138,10 +135,10 @@ def stub_original_commit_message(message)
138135
# comment III
139136
COMMIT_MESSAGE
140137

141-
stub_original_commit_message("# comment #1\n# comment B\n# comment III")
138+
write_commit_editmsg_file("# comment #1\n# comment B\n# comment III")
142139
commit_message.append("[#8675309]")
143140

144-
expect(fake_file.content).to eq(commit_message_text)
141+
expect(commit_editmsg_file.read).to eq(commit_message_text)
145142
end
146143
end
147144
end

spec/git_tracker/repository_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
RSpec.describe GitTracker::Repository do
44
subject(:repository) { described_class }
55
let(:git_command) { "git rev-parse --show-toplevel" }
6+
67
before do
78
allow_message_expectations_on_nil
89
allow(repository).to receive(:`).with(git_command) { "/path/to/git/repo/root\n" }

spec/spec_helper.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
require "pry-byebug"
77
require_relative "support/commit_message_helper"
8-
require_relative "support/fake_file"
98
require_relative "support/output_helper"
109
require_relative "support/matchers/exit_code_matchers"
1110

spec/support/commit_message_helper.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
module CommitMessageHelper
2-
def example_commit_message(pattern_to_match)
2+
def example_commit_message(pattern:)
33
<<~EXAMPLE
44
Got Jenny's number, gonna' make her mine!
55
6-
#{pattern_to_match}
6+
#{pattern}
77
# Please enter the commit message for your changes. Lines starting
88
# with '#' will be ignored, and an empty message aborts the commit.
9-
# On branch get_jennys_number_#8675309
9+
# On branch just_a_branchy_##{pattern}
1010
# Changes to be committed:
1111
# (use "git reset HEAD <file>..." to unstage)
1212
#
@@ -15,4 +15,17 @@ def example_commit_message(pattern_to_match)
1515
1616
EXAMPLE
1717
end
18+
19+
# NOTE: The default value of `file` is ✨ magically ✨ assumed to just exist.
20+
# So either pass it in explicitly, or use a `let` to define it.
21+
def setup_commit_editmsg_file(story_text, file: commit_editmsg_file)
22+
body = example_commit_message(pattern: story_text)
23+
write_commit_editmsg_file(body, file: file)
24+
end
25+
26+
def write_commit_editmsg_file(body, file: commit_editmsg_file)
27+
Pathname(file).open("w") do |f|
28+
f.write(body)
29+
end
30+
end
1831
end

spec/support/fake_file.rb

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)