defmodule TerminusDB.Merge do
@moduledoc """
Branch merge API for TerminusDB.
Wraps the `/api/rebase` endpoint to merge (rebase) a source branch into a target
branch. TerminusDB uses a rebase model: the source branch's commits are
replayed on top of the target branch, creating a linear history.
## Quick start
config =
TerminusDB.Config.new(endpoint: "http://localhost:6363")
|> TerminusDB.Config.with_database("mydb")
# Merge `feature` into `main`
{:ok, result} = TerminusDB.Merge.merge(config,
source_branch: "feature",
target_branch: "main"
)
"""
alias TerminusDB.{Client, Config, Error}
@type merge_opt ::
{:source_branch, String.t()}
| {:target_branch, String.t()}
| {:organization, String.t()}
| {:repo, String.t()}
| {:author, String.t()}
| {:message, String.t()}
defp build_resource(config, branch, opts) do
org = opts[:organization] || config.organization
db = config.database || raise Error, reason: :http, message: "no database scoped in config"
repo = opts[:repo] || config.repo
"#{org}/#{db}/#{repo}/branch/#{branch}"
end
defp rebase_path(config, opts) do
org = opts[:organization] || config.organization
db = config.database || raise Error, reason: :http, message: "no database scoped in config"
repo = opts[:repo] || config.repo
target = opts[:target_branch] || config.branch
"rebase/#{org}/#{db}/#{repo}/branch/#{target}"
end
defp rebase_body(config, opts) do
source_branch = Keyword.fetch!(opts, :source_branch)
source = build_resource(config, source_branch, opts)
author = opts[:author] || "admin"
message = opts[:message] || "rebase #{source_branch}"
%{
"author" => author,
"message" => message,
"rebase_from" => source
}
end
@doc """
Merges (rebases) the `source_branch` into the `target_branch`.
TerminusDB replays the source branch's commits on top of the target branch.
If there are conflicts, the operation returns an error describing them.
## Options
- `:source_branch` (required) - the branch to merge from.
- `:target_branch` - the branch to merge into (default: `config.branch`).
- `:organization` - overrides `config.organization`.
- `:repo` - overrides `config.repo`.
- `:author` - commit author for the merge commit.
- `:message` - commit message for the merge commit.
## Examples
iex> config = TerminusDB.Config.new(
...> endpoint: "http://localhost:6363",
...> adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
...> ) |> TerminusDB.Config.with_database("mydb")
iex> {:ok, result} = TerminusDB.Merge.merge(config,
...> source_branch: "feature",
...> target_branch: "main"
...> )
iex> result["api:status"]
"api:success"
"""
@spec merge(Config.t(), [merge_opt()]) :: {:ok, map()} | {:error, Error.t()}
def merge(config, opts \\ []) do
path = rebase_path(config, opts)
body = rebase_body(config, opts)
Client.request(config, :post, path, json: body, area: :merge)
end
@doc """
Merges branches, or raises `TerminusDB.Error`.
## Examples
iex> config = TerminusDB.Config.new(
...> endpoint: "http://localhost:6363",
...> adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
...> ) |> TerminusDB.Config.with_database("mydb")
iex> TerminusDB.Merge.merge!(config, source_branch: "feature", target_branch: "main")
%{"api:status" => "api:success"}
"""
@spec merge!(Config.t(), [merge_opt()]) :: map()
def merge!(config, opts \\ []) do
case merge(config, opts) do
{:ok, body} -> body
{:error, error} -> raise error
end
end
end