Skip to content

Allow seperate app versions under an Umbrella #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,27 @@ end
version: String.trim(File.read!("VERSION")),
```

## Usage
## Usage - Normal Apps

```bash
$ mix eliver.bump
```

## Usage - Umbrella Apps

In umbrella apps there are two supported use-cases:

1. A single version file is provided, with all sub-apps sharing the same version number.
2. Sub-app versions are managed seperately. In this case, each app has a seperate `VERSION` and `CHANGELOG.md` file.

For option 1, eliver is used in the same manner as for normal Elixir apps.

For option 2, eliver provides a `--multi` flag.

```bash
$ mix eliver.bump --multi
```

## Contributing

Please do.
1 change: 1 addition & 0 deletions lib/eliver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Eliver do
def next_version(version_number, bump_type) do
[major, minor, patch] = String.split(version_number, ".") |> Enum.map(&String.to_integer/1)
case bump_type do
:none -> [major, minor, patch]
:major -> [major + 1, 0, 0]
:minor -> [major, minor + 1, 0]
:patch -> [major, minor, patch + 1]
Expand Down
65 changes: 62 additions & 3 deletions lib/eliver/git.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,40 @@ defmodule Eliver.Git do
git "tag", ["#{new_version}", "-a", "-m", "Version: #{new_version}"]
end

def push!(new_version) do
git "push", ["-q", "origin", current_branch(), new_version]
def commit!(commit_changes) do
for change <- commit_changes do
git "add", "#{elem(change, 1)}/CHANGELOG.md"
git "add", "#{elem(change, 1)}/VERSION"
end

umbrella_changes = Enum.find(commit_changes,
fn({app, app_path, current_version, new_version, changelog_entries}) ->
app == :umbrella
end
)
app_changes = List.delete(commit_changes, umbrella_changes)
Comment on lines +50 to +55
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be combined in a with


git "commit", ["-m", aggregated_commit_message(umbrella_changes, app_changes)]

Enum.map(commit_changes, fn({app, app_path, _current_version, new_version, changelog_entries}) ->
if app == :umbrella do
git "tag", ["#{new_version}", "-a", "-m", "Version: #{new_version}"]

"#{new_version}"
else
git "tag", ["#{app_path}/#{new_version}", "-a", "-m", "Version(#{Atom.to_string(app)}): #{new_version}"]

"#{app_path}/#{new_version}"
end
end)
end

def push!(tags) when is_list(tags) do
attributes = ["-q", "origin", current_branch()] ++ tags
git "push", attributes
end
def push!(tag) do
git "push", ["-q", "origin", current_branch(), tag]
end

defp git(command, args) when is_list(args) do
Expand Down Expand Up @@ -72,6 +104,33 @@ Version #{new_version}:
#{Enum.map(changelog_entries, fn(x) -> "* " <> x end) |> Enum.join("\n")}
"""
end
defp aggregated_commit_message(umbrella_changes, app_changes) when not is_nil(app_changes) do
"""
Version #{elem(umbrella_changes, 3)}:

#{Enum.map(elem(umbrella_changes, 4), fn(x) -> "* " <> x end) |> Enum.join("\n")}

Nested Changes:
#{Enum.flat_map(app_changes, fn(app_change) ->
[nested_commit_version(elem(app_change, 1), elem(app_change, 3))]
++
nested_commit_messages(elem(app_change, 4))
end) |> Enum.join("\n")}
"""
end
defp aggregated_commit_message(umbrella_changes, _app_changes) do
"""
Version #{elem(umbrella_changes, 3)}:

#{Enum.map(elem(umbrella_changes, 4), fn(x) -> "* " <> x end) |> Enum.join("\n")}
"""
end

defp nested_commit_version(app, version) do
"\t#{app} - #{version}"
end
defp nested_commit_messages(change_strings) do
Enum.map(change_strings, fn(change_string) -> "\t\t" <> change_string end)
end

end
end
11 changes: 11 additions & 0 deletions lib/eliver/multiple.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Eliver.Multiple do

def list_sub_apps() do
Mix.Project.apps_paths()
|> case do
nil -> {:error, :unknown_app_structure}
sub_apps -> {:ok, sub_apps}
end
end

end
138 changes: 124 additions & 14 deletions lib/mix/tasks/eliver.bump.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,129 @@ defmodule Mix.Tasks.Eliver.Bump do
args |> parse_args |> bump
end

defp bump(_) do
defp bump(args) do
case check_for_git_problems() do
{:error, message} ->
say message, :red
{:ok} ->
{new_version, changelog_entries} = get_changes()
if allow_changes?(new_version, changelog_entries) do
make_changes(new_version, changelog_entries)
do_bump(args)
end
end

defp do_bump(args) do
case Keyword.get(args, :multi) do
true -> bump_multiple()
nil -> bump_normal()
end
end

defp bump_multiple() do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could make sense to try to split this one into smaller functions so that it's a bit more understandable what it does, wdyt ?

Eliver.Multiple.list_sub_apps()
|> case do
{:ok, sub_apps} ->
sub_apps = Map.put(sub_apps, :umbrella, ".")

changes = Enum.map(sub_apps, fn

{:umbrella, app_path} ->
# Note changes re required for the umbrella app
{current_version, new_version, changelog_entries} = get_changes(app_path, :normal)
{:umbrella, app_path, current_version, new_version, changelog_entries}

{app, app_path} ->

say "\n\n=============================================="
say "Bump details for #{Atom.to_string(app)}"
say "=============================================="

get_changes(app_path, :multi)
|> case do
nil -> nil
{current_version, new_version, changelog_entries} ->
{app, app_path, current_version, new_version, changelog_entries}
end


end)
|> Enum.filter(fn(change) -> not is_nil(change) end)

if allow_changes?(changes) do
make_changes(changes)
end

{:error, :unknown_app_structure} ->
say "Please note that only standard umbrella app directory structures are supported. Please refer to the documentation for details", :red
end
Comment on lines +28 to +61
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reckon this could be used with a with statement and relying on pattern matching to do the thing.

end
defp bump_normal() do
{current_version, new_version, changelog_entries} = get_changes()
if allow_changes?(new_version, changelog_entries) do
make_changes(new_version, changelog_entries)
end
end

defp get_changes do
{get_new_version(), get_changelog_entries()}
defp get_changes(root_for_changes \\ ".", bump_type \\ :normal) do
{current_version, new_version} = get_new_version(root_for_changes, bump_type)

if current_version == new_version do
nil
else
{current_version, new_version, get_changelog_entries()}
end
end

defp allow_changes?(changes) when is_list(changes) do
say "\n"
say "=============================================="
say "Summary of changes:"
say "=============================================="

Enum.each(changes, fn(app_changes) ->
display_change(app_changes)
end)

say "\n"
result = ask "Continue?", false
case result do
{:ok, value} -> value
{:error, _} -> false
end
end
defp allow_changes?(new_version, changelog_entries) do
current_version = Eliver.VersionFile.version
say "\n"
say "Summary of changes:"
say "Bumping version #{current_version} → #{new_version}", :green
say ("#{Enum.map(changelog_entries, fn(x) -> "* " <> x end) |> Enum.join("\n")}"), :green
say "\n"
display_change(current_version, new_version, changelog_entries)
result = ask "Continue?", false
case result do
{:ok, value} -> value
{:error, _} -> false
end
end

defp display_change({app, app_path, current_version, new_version, changelog_entries}) do
say "\n#{Atom.to_string(app)} (#{app_path})"
say "=============================================="
display_change(current_version, new_version, changelog_entries)
end
defp display_change(current_version, new_version, changelog_entries) do
say "Bumping version #{current_version} → #{new_version}", :green
say ("#{Enum.map(changelog_entries, fn(x) -> "* " <> x end) |> Enum.join("\n")}"), :green
say "\n"
end

defp make_changes(changes) when is_list(changes) do
for {app, app_path, current_version, new_version, changelog_entries} <- changes do
Eliver.VersionFile.bump(new_version, "#{app_path}/VERSION")
Eliver.ChangeLogFile.bump(new_version, changelog_entries, "#{app_path}/CHANGELOG.md")
end

tags = Eliver.Git.commit!(changes)

say "Pushing to origin..."
Eliver.Git.push!(tags)
end

defp make_changes(new_version, changelog_entries) do
Eliver.VersionFile.bump(new_version)
Eliver.ChangeLogFile.bump(new_version, changelog_entries)
Expand Down Expand Up @@ -68,16 +161,33 @@ defmodule Mix.Tasks.Eliver.Bump do
end
end

defp get_new_version do
Eliver.VersionFile.version |> Eliver.next_version(get_bump_type())
defp get_new_version(dir, type) do
{
Eliver.VersionFile.version("#{dir}/VERSION"),
Eliver.VersionFile.version("#{dir}/VERSION") |> Eliver.next_version(get_bump_type(type))
}
end

defp get_changelog_entries do
{:ok, result} = get_list "Enter the changes, enter a blank line when you're done"
result
end

defp get_bump_type do
defp get_bump_type(:multi) do
result = choose "Select release type",
patch: "Bug fixes, recommended for all",
minor: "New features, but backwards compatible",
major: "Breaking changes",
none: "None",
default: :none

case result do
{:ok, value} -> value
{:error, _} -> get_bump_type(:multi)
end
end

defp get_bump_type(:normal) do
result = choose "Select release type",
patch: "Bug fixes, recommended for all",
minor: "New features, but backwards compatible",
Expand All @@ -86,12 +196,12 @@ defmodule Mix.Tasks.Eliver.Bump do

case result do
{:ok, value} -> value
{:error, _} -> get_bump_type()
{:error, _} -> get_bump_type(:normal)
end
end

defp parse_args(args) do
{_, args, _} = OptionParser.parse(args)
{args, _, _} = OptionParser.parse(args)
args
end
end