Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ For an example, see this project's [CHANGELOG.md](https://github.com/zachdaniel/
Roadmap (in no particular order):

* More tests
* Support multiple changes in a single commit via multiple conventional commits
in a single commit
* Automatically parse issue numbers and github mentions into the correct format,
linking the issue
* A task to build a compliant commit
Expand Down
75 changes: 51 additions & 24 deletions lib/git_ops/commit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ defmodule GitOps.Commit do
# 40/41 are `(` and `)`, but syntax highlighters don't like ?( and ?)
type =
optional(whitespace)
|> tag(ascii_string([not: ?:, not: ?!, not: 40, not: 41], min: 1), :type)
|> optional(whitespace)
|> tag(ascii_string([not: ?:, not: ?!, not: 40, not: 41, not: 10, not: 32], min: 1), :type)
|> optional(whitespace)

scope =
Expand All @@ -33,13 +34,30 @@ defmodule GitOps.Commit do

message = tag(optional(whitespace), ascii_string([not: ?\n], min: 1), :message)

defparsecp(
:commit,
commit =
type
|> concat(optional(scope))
|> concat(optional(breaking_change_indicator))
|> ignore(ascii_char([?:]))
|> concat(message),
|> concat(message)
|> concat(optional(whitespace))
|> concat(optional(ignore(ascii_string([10], min: 1))))

body =
[commit, eos()]
|> choice()
|> lookahead_not()
|> utf8_char([])
|> repeat()
|> reduce({List, :to_string, []})
|> tag(:body)

defparsecp(
:commits,
commit
|> concat(body)
|> tag(:commit)
|> repeat(),
inline: true
)

Expand Down Expand Up @@ -77,26 +95,35 @@ defmodule GitOps.Commit do
end

def parse(text) do
case commit(text) do
{:ok, result, remaining, _state, _dunno, _also_dunno} ->
remaining_lines =
remaining
|> String.split("\n")
|> Enum.map(&String.trim/1)
|> Enum.reject(&Kernel.==(&1, ""))

body = Enum.at(remaining_lines, 0)
footer = Enum.at(remaining_lines, 1)

{:ok,
%__MODULE__{
type: Enum.at(result[:type], 0),
scope: scopes(result[:scope]),
message: Enum.at(result[:message], 0),
body: body,
footer: footer,
breaking?: is_breaking?(result[:breaking?], body, footer)
}}
case commits(text) do
{:ok, [], _, _, _, _} ->
:error

{:ok, results, _remaining, _state, _dunno, _also_dunno} ->
commits =
Enum.map(results, fn {:commit, result} ->
remaining_lines =
result[:body]
|> Enum.map(&String.trim/1)
|> Enum.join("\n")
|> String.split("\n")
|> Enum.map(&String.trim/1)
|> Enum.reject(&Kernel.==(&1, ""))

body = Enum.at(remaining_lines, 0)
footer = Enum.at(remaining_lines, 1)

%__MODULE__{
type: Enum.at(result[:type], 0),
scope: scopes(result[:scope]),
message: Enum.at(result[:message], 0),
body: body,
footer: footer,
breaking?: is_breaking?(result[:breaking?], body, footer)
}
end)

{:ok, commits}

{:error, _message, _remaining, _state, _dunno, _also_dunno} ->
:error
Expand Down
22 changes: 14 additions & 8 deletions lib/mix/tasks/git_ops.release.ex
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,8 @@ defmodule Mix.Tasks.GitOps.Release do

defp parse_commit(text, config_types, log?) do
case Commit.parse(text) do
{:ok, commit} ->
if Map.has_key?(config_types, String.downcase(commit.type)) do
[commit]
else
error_if_log("Commit with unknown type: #{text}", log?)

[]
end
{:ok, commits} ->
commits_with_type(config_types, commits, text, log?)

_ ->
error_if_log("Unparseable commit: #{text}", log?)
Expand All @@ -231,6 +225,18 @@ defmodule Mix.Tasks.GitOps.Release do
end
end

defp commits_with_type(config_types, commits, text, log?) do
Enum.flat_map(commits, fn commit ->
if Map.has_key?(config_types, String.downcase(commit.type)) do
[commit]
else
error_if_log("Commit with unknown type in: #{text}", log?)

[]
end
end)
end

defp append_changes_to_message(message, _, {:error, :bad_replace}), do: message

defp append_changes_to_message(message, file, changes) do
Expand Down
55 changes: 45 additions & 10 deletions test/commit_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,30 @@ defmodule GitOps.Test.CommitTest do

alias GitOps.Commit

defp format!(message) do
defp format_one!(message) do
message
|> parse!()
|> parse_one!()
|> Commit.format()
end

defp parse!(message) do
{:ok, commit} = Commit.parse(message)
defp parse_one!(message) do
{:ok, [commit]} = Commit.parse(message)

commit
end

defp parse_many!(message) do
{:ok, commits} = Commit.parse(message)

commits
end

test "a simple feature is parsed with the correct type" do
assert parse!("feat: An awesome new feature!").type == "feat"
assert parse_one!("feat: An awesome new feature!").type == "feat"
end

test "a simple feature is parsed with the correct message" do
assert parse!("feat: An awesome new feature!").message == "An awesome new feature!"
assert parse_one!("feat: An awesome new feature!").message == "An awesome new feature!"
end

@tag :regression
Expand All @@ -29,18 +35,47 @@ defmodule GitOps.Test.CommitTest do
end

test "a breaking change via a postfixed exclamation mark is parsed as a breaking change" do
assert parse!("feat!: A breaking change").breaking?
assert parse_one!("feat!: A breaking change").breaking?
end

test "a breaking change via a postfixed exclamation mark after a scope is parsed as a breaking change" do
assert parse!("feat(stuff)!: A breaking change").breaking?
assert parse_one!("feat(stuff)!: A breaking change").breaking?
end

test "a simple feature is formatted correctly" do
assert format!("feat: An awesome new feature!") == "* An awesome new feature!"
assert format_one!("feat: An awesome new feature!") == "* An awesome new feature!"
end

test "a breaking change does not include the exclamation mark in the formatted version" do
assert format!("feat!: An awesome new feature!") == "* An awesome new feature!"
assert format_one!("feat!: An awesome new feature!") == "* An awesome new feature!"
end

test "multiple messages can be parsed from a commit" do
text = """
fix: fixed a bug

some text about it

some even more data about it

improvement: improved a thing

some other text about it

some even more text about it
"""

assert [
%Commit{
message: "fixed a bug",
body: "some text about it",
footer: "some even more data about it"
},
%Commit{
message: "improved a thing",
body: "some other text about it",
footer: "some even more text about it"
}
] = parse_many!(text)
end
end