Composable helpers for common multi-step git workflows.
Each function orchestrates several lower-level Git commands into a
single logical operation. All functions accept a keyword list where the
:config key, when present, must be a Git.Config struct and is
forwarded to every underlying git invocation.
Examples
# Stage everything and commit in one call
{:ok, result} = Git.Workflow.commit_all("feat: ship it", config: cfg)
# Work on a feature branch, then return to the original branch
{:ok, result} = Git.Workflow.feature_branch("feat/cool", fn opts ->
File.write!("cool.txt", "cool")
{:ok, :done} = Git.add(files: ["cool.txt"], config: opts[:config])
{:ok, _} = Git.commit("feat: cool", Keyword.take(opts, [:config]))
{:ok, :worked}
end, merge: true, config: cfg)
Summary
Types
A step, optionally labeled so a failure can name it.
A workflow step: a 1-arity function receiving the config keyword list.
Functions
Amends the last commit.
Cherry-picks commits onto another branch, then returns to where you started.
Runs a list of steps in order, threading :config into each and
short-circuiting on the first {:error, _}.
Stages all changes and commits with the given message.
Returns the working tree to a pristine committed state.
Creates a feature branch, runs a function on it, and returns to the original branch.
Pushes the current branch to a remote, setting its upstream.
Creates an annotated release tag and, optionally, pushes it.
Recreates a deleted branch.
Rebases onto an upstream, cleaning up on conflict.
Collapses the last count commits on the current branch into a single new
commit with message.
Merges a branch with --squash and commits with the given message.
Fetches from a remote and integrates changes using rebase or merge.
Syncs a fork: fetches the upstream and integrates it into the current branch.
Merges branch into the current branch, cleaning up on conflict.
Moves HEAD back by :count commits, returning the commits that were undone.
Checks out ref, runs fun on it, then restores the original branch.
Stashes uncommitted tracked changes (only if the tree is dirty), runs fun,
then pops the stash.
Types
Functions
@spec amend(keyword()) :: {:ok, Git.CommitResult.t()} | {:error, term()}
Amends the last commit.
When no :message is provided, the existing commit message is reused.
When :all is true, all changes are staged before amending.
Options
:message- new commit message (default: reuse existing message):all- stage all changes before amending (defaultfalse):config- aGit.Configstruct
Returns {:ok, commit_result} on success.
@spec backport( String.t() | [String.t()], keyword() ) :: {:ok, Git.CherryPickResult.t()} | {:error, term()}
Cherry-picks commits onto another branch, then returns to where you started.
commits is a single sha/ref or a list of them, applied in order. The current
branch is remembered, :target is checked out (created from :base when
given), and the commits are cherry-picked. On a conflict the cherry-pick is
aborted; the original branch is always restored afterward, even if a step
raises. With :push, the target is pushed to :remote after a clean pick.
Options
:target- branch to apply the commits on (required):base- create:targetfrom this ref first (default::targetmust exist):push- push:targetto:remoteafter a clean pick (defaultfalse):remote- remote to push to (default"origin"):config- aGit.Configstruct
Returns {:ok, %Git.CherryPickResult{}} on success, or the error.
@spec chain( [labeled_step()], keyword() ) :: {:ok, term()} | {:error, term()}
Runs a list of steps in order, threading :config into each and
short-circuiting on the first {:error, _}.
Each step is a 1-arity function receiving the config keyword list (the same
contract as feature_branch/3's function), or a {name, fun} tuple. With a
tuple, a failure is reported as {:error, {name, reason}}; otherwise the raw
{:error, reason} is returned. Returns the last step's {:ok, result}; an
empty list returns {:ok, nil}.
Steps do not receive each other's results. Prefer a plain with for a fixed
sequence or when a later step needs an earlier step's output; reach for
chain/2 when the step list is built at runtime.
Examples
[
{:stage, fn o -> Git.add(Keyword.merge(o, all: true)) end},
{:commit, fn o -> Git.commit("chore: release", o) end}
]
|> Git.Workflow.chain(config: cfg)
@spec commit_all( String.t(), keyword() ) :: {:ok, Git.CommitResult.t()} | {:error, term()}
Stages all changes and commits with the given message.
Any additional keyword options (e.g., :allow_empty) are forwarded to
Git.commit/2.
Options
:config- aGit.Configstruct- All other options are passed to
Git.commit/2.
Returns {:ok, commit_result} on success.
@spec discard_all(keyword()) :: {:ok, :discarded} | {:ok, {:dry_run, [String.t()]}} | {:error, term()}
Returns the working tree to a pristine committed state.
Hard-resets tracked changes and removes untracked files and directories, so
both halves of a dirty tree are cleared (a reset --hard leaves untracked
files, a clean leaves tracked modifications; you need both).
Destructive: discarded untracked and ignored files are not recoverable.
Preview first with dry_run: true.
Options
:ignored- also remove ignored files (clean -x, defaultfalse):dry_run- do not change anything; return the untracked/ignored files that would be removed (defaultfalse):config- aGit.Configstruct
Returns {:ok, :discarded} after clearing the tree, or
{:ok, {:dry_run, paths}} when :dry_run is set.
@spec feature_branch( String.t(), (keyword() -> {:ok, term()} | {:error, term()}), keyword() ) :: {:ok, term()} | {:error, term()}
Creates a feature branch, runs a function on it, and returns to the original branch.
The function fun receives a keyword list containing the :config key
(when one was provided in opts). It must return {:ok, result} or
{:error, reason}.
After fun completes (successfully or not), the original branch is checked
out to ensure cleanup.
Options
:merge- whentrue, merge the feature branch back into the original branch afterfunsucceeds (defaultfalse):delete- whentrue, delete the feature branch after a successful merge (defaultfalse; requires:mergeto betrue):config- aGit.Configstruct
Returns {:ok, result} where result is the return value of fun, or
the merge result when :merge is true.
Pushes the current branch to a remote, setting its upstream.
Runs git push -u <remote> <current-branch>. With :force, uses
--force-with-lease rather than a plain push, so a remote that moved on is not
overwritten blindly.
Options
:remote- remote to push to (default"origin"):force- force with lease (defaultfalse):config- aGit.Configstruct
Returns {:ok, :done} on success.
Creates an annotated release tag and, optionally, pushes it.
Refuses to clobber an existing tag: when version already exists the tag is
left untouched and {:error, {:tag_exists, version}} is returned. Publishing
is opt-in, so nothing is pushed unless :push is true.
Options
:message- annotation message (default:version):sign- create a GPG-signed tag (-s, defaultfalse):ref- commit to tag (default"HEAD"):push- push the tag after creating it (defaultfalse):remote- remote to push the tag to (default"origin"):config- aGit.Configstruct
Returns {:ok, version} once the tag exists (and, with :push, is pushed).
Recreates a deleted branch.
With :sha, simply creates name at that commit. Otherwise scans the reflog
for the last commit name pointed at (the value HEAD held just before the
branch was last left) and recreates it there, or returns
{:error, :not_found} when the reflog has no record of the branch. Pass
checkout: true to switch to the restored branch.
Options
:sha- recreate the branch at this commit instead of consulting the reflog:checkout- check the branch out after recreating it (defaultfalse):config- aGit.Configstruct
Returns {:ok, sha} with the commit the branch was recreated at.
@spec safe_rebase(keyword()) :: {:ok, Git.RebaseResult.t()} | {:error, term()}
Rebases onto an upstream, cleaning up on conflict.
Runs Git.rebase/1 with :upstream (and optional :onto) taken from opts.
A conflict (or any rebase failure) leaves the rebase in progress, so this
aborts it with Git.rebase(abort: true, ...) before returning the original
{:error, reason}. The abort is best-effort.
Options
:upstream- the upstream ref to rebase onto:onto- rebase onto this ref instead of the upstream's base:config- aGit.Configstruct
Returns {:ok, %Git.RebaseResult{}} on a clean rebase.
@spec squash_last(pos_integer(), String.t(), keyword()) :: {:ok, Git.CommitResult.t()} | {:error, term()}
Collapses the last count commits on the current branch into a single new
commit with message.
Soft-resets to HEAD~count (keeping all their changes staged) and commits
them as one. This is the only in-place squash in the library, since
interactive rebase is intentionally not wrapped. count must be at least 2.
Rewrites current-branch history; recover the originals from the reflog if needed.
Options
:config- aGit.Configstruct- All other options are forwarded to
Git.commit/2.
Returns {:ok, commit_result}, or {:error, :cannot_undo_root} when there
are not enough commits to squash that many.
@spec squash_merge( String.t(), keyword() ) :: {:ok, Git.CommitResult.t()} | {:error, term()}
Merges a branch with --squash and commits with the given message.
Options
:message- commit message (required):delete- whentrue, delete the source branch after merge (defaultfalse):config- aGit.Configstruct
Returns {:ok, commit_result} on success.
Fetches from a remote and integrates changes using rebase or merge.
Options
:strategy-:rebase(default) or:merge:autostash- stash uncommitted changes before syncing and pop after (defaulttrue). Like git's ownrebase.autostash, only tracked changes are stashed; an untracked-only working tree is not stashed.:remote- remote name (default"origin"):branch- branch to sync with (defaults to the upstream tracking branch):config- aGit.Configstruct
Returns {:ok, :synced} on success.
When autostash is active and integration succeeds but popping the stash
conflicts with the freshly integrated changes, returns
{:error, {:autostash_pop_failed, reason}} (the working tree is left with the
popped stash and its conflict markers) rather than reporting success over it.
When integration itself fails (for example a rebase or merge conflict), the
autostash is left in place; recover it with git stash pop after resolving
the integration, since popping onto a mid-conflict tree would compound it.
Syncs a fork: fetches the upstream and integrates it into the current branch.
Fetches :upstream, then fast-forwards the current branch onto
<upstream>/<branch> (a --ff-only merge, the default) or rebases onto it
when strategy: :rebase. With :push, the result is pushed to :origin.
Options
:upstream- remote to sync from (default"upstream"):origin- remote to push to when:push(default"origin"):branch- branch to track on the upstream (default: the current branch):strategy-:merge(fast-forward only, default) or:rebase:push- push to:originafter integrating (defaultfalse):config- aGit.Configstruct
Returns {:ok, :synced} on success.
@spec try_merge( String.t(), keyword() ) :: {:ok, Git.MergeResult.t()} | {:error, term()}
Merges branch into the current branch, cleaning up on conflict.
Runs Git.merge/2. A conflict (or any merge failure) leaves the working tree
mid-merge, so this aborts it with Git.merge(:abort, ...) before returning the
original {:error, reason}. The abort is best-effort: when the failure left no
merge to abort, the abort's own error is ignored and the original merge error
is still returned.
Extra options (for example :no_ff or :strategy) are forwarded to the merge.
Options
:config- aGit.Configstruct- All other options are forwarded to
Git.merge/2.
Returns {:ok, %Git.MergeResult{}} on a clean merge.
@spec undo_last_commit(keyword()) :: {:ok, [Git.Commit.t()]} | {:error, term()}
Moves HEAD back by :count commits, returning the commits that were undone.
This is the "I did not mean to commit that" helper. It captures the commits
being undone first, then resets. :mode controls what happens to their
changes:
:soft(default) - keep the changes staged:mixed- keep the changes in the working tree, unstaged:hard- discard the changes (irreversible beyond the reflog)
Options
:count- number of commits to undo (default1):mode-:soft(default),:mixed, or:hard:config- aGit.Configstruct
Returns {:ok, undone_commits} where undone_commits is the list of
Git.Commit structs that were on top of the new HEAD, or
{:error, :cannot_undo_root} when there is not enough history to move back
that far (you cannot reset past the root commit).
Checks out ref, runs fun on it, then restores the original branch.
The original branch is restored even if fun raises. Pass create: true to
create the branch first. Returns fun's result. This is the reusable bracket
underneath feature_branch/3.
Options
:create- createrefas a new branch before running (defaultfalse):config- aGit.Configstruct
Stashes uncommitted tracked changes (only if the tree is dirty), runs fun,
then pops the stash.
The stash is popped even if fun raises. Returns fun's result. A pop that
fails (for example a conflict) is surfaced as the error rather than hidden.
This is the :autostash behavior of sync/1, made reusable.
Options
:config- aGit.Configstruct