# `Ramal.Merge`
[🔗](https://github.com/agustinrojas1/ramal/blob/v0.2.0/lib/ramal/merge.ex#L1)

Merges a branch into the base: checks that the base did not move under the branch, then replays the branch's log
through the application's real actions inside one transaction.

`Ramal.merge/2` is the public entry point. The steps, in order:

  1. The branch process is claimed, which fails unless the branch is open and not already being merged. While it is
     claimed the process refuses writes, reads of its slice, discards and a second merge.
  2. Every base row the branch fingerprinted is read again, locked (`FOR UPDATE`) where the data layer supports it,
     and compared. A row that is gone or whose fingerprint differs is stale. `on_stale` decides what happens.
  3. The log is replayed oldest first with no branch current, so every action runs for real: validations and
     policies see the base as it is now. Entries marked `nested?` are skipped, because replaying the action that
     wrote them runs their hook again.
  4. On success the branch is marked `:merged`, persisted, and its process stops. On any failure the transaction is
     rolled back and the branch stays open.

Rows a replayed create writes keep the primary key they had in the branch (the key is forced onto the changeset), so
later log entries and the branch's own diff still name the right rows.

## Options

  * `:actor` - the actor for every replayed action. Default: the actor each entry was recorded with.
  * `:authorize?` - default `true`
  * `:on_stale` - `:error` (default), `:ignore`, or a function from the stale rows to `:continue` or `:abort`
  * `:mode` - only `:replay`

Data layers that cannot run transactions, such as `Ash.DataLayer.Ets`, cannot roll a failed replay back. The staleness
check still runs before the first write, so a stale base writes nothing. A replayed action that fails after earlier
entries were written leaves those writes in place, the branch stays open, and the `:merge_failed` error lists the
resources in `details.not_rolled_back`. `Ash.transact/3` runs the function as it is for such a data layer and returns
its `{:error, _}` inside `{:ok, _}`, which the merge unwraps.

Known limits, documented in `docs/adr/0007-merge.md`: tenants are refused inside a branch (ADR 0009), so the staleness check has none, a top-level action
that targets a row created by a hook cannot be replayed, and changes made with `Ash.Changeset.change_attribute/3`
outside the action's own inputs are not in the log.

## Example

    {:ok, %Ramal.Merge.Result{replayed: 1}} = Ramal.merge(branch, actor: current_user)

# `run`

```elixir
@spec run(String.t(), keyword()) ::
  {:ok, Ramal.Merge.Result.t()} | {:error, Ramal.Error.t()}
```

Merges the branch with `id`. See the module doc for the steps and the options.

Returns `{:error, %Ramal.Error{reason: :stale}}` with the stale rows in `details.rows`, `:merge_failed` with the log
index, resource, action, Ash error and `not_rolled_back` resources in `details` when a replayed action fails,
`:not_open` for a merged, discarded or already-merging branch, and `:nested_branch` when called inside `Ramal.run/2`.

## Example

    {:error, %Ramal.Error{reason: :stale, details: %{rows: rows}}} = Ramal.merge(branch)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
