Skip to content

realityforge/braid

Repository files navigation

Braid

Braid is a simple tool to help track vendor branches in a Git repository. Braid mirrors selected files or directories from upstream Git repositories into a downstream repository.

This is a port of the Ruby Braid implemented in Go and built with Bazel.

For migration-impacting differences from Ruby Braid, see docs/migration-from-ruby-braid.md.

Motivation

Vendoring allows you take the source code of an external library and ensure it's version controlled along with the main project. This is in contrast to including a reference to a packaged version of an external library that is available in a binary artifact repository such as Maven Central, RubyGems or NPM.

Vendoring is useful when you need to patch or customize the external libraries or the external library is expected to co-evolve with the main project. The developer can make changes to the main project and patch the library in a single commit.

The problem arises when the external library makes changes that you want to integrate into your local vendored version or the developer makes changes to the local version that they want integrated into the external library.

A typical "implementation" of vendoring is to simply download or checkout the source for the external library, remove the .git or .svn directories and commit it to the main source tree. However this approach makes it very difficult to update the library. When you want to update the library do you re-apply your local changes onto a new copy of the vendored library or do you re-apply the changes from the external library to local version? Both cases involve manual generation and application of patch files to source trees.

This is where Braid comes into play. Braid makes it easy to vendor in remote git repositories and use an automated mechanism for updating the external library and generating patches to upgrade the external library.

Braid creates a file .braids.json in the root of your repository that contains references to external libraries or mirrors. The configuration allows you to control aspects of the mirroring process such as;

  • whether the mirror is locked to a particular version of the external library.
  • whether the mirror is tracking a tag or a branch.
  • whether the mirror includes the entire external library or just a subdirectory.

Install

For a release build, download the artifact for your platform from the release page, verify SHA256SUMS, make the binary executable on Unix-like systems, and place it on PATH.

shasum -a 256 -c SHA256SUMS
chmod +x braid-linux-amd64
sudo install -m 0755 braid-linux-amd64 /usr/local/bin/braid

For local development, run the binary through Bazel:

bazel run //cmd/braid:braid -- version

Usage

Braid commands run from any directory inside a Git working tree. Commands that create automatic commits (add, update, and remove) require their command-owned paths to be clean, block unresolved Git operations, and leave unrelated staged, unstaged, and untracked work untouched and out of Braid's commits. status, diff, and push are the usual commands for deciding whether to update, prepare a patch, or send local mirror changes upstream.

Command Form

Global flags must appear before the command name:

braid [--verbose|-v] [--no-cache | --cache-dir <path>] <command> [options]

Use the built-in help for the command list or command-specific syntax:

braid help
braid add help
braid add --help

Quick Start

Start anywhere inside an existing Git repository. Add a mirror at the path where you want the upstream content to live, relative to your current directory:

braid add <upstream-git-url> lib/grit

Braid copies the upstream content into lib/grit, records the mirror in .braids.json, and creates a Braid: Add mirror ... commit. If you do not specify --branch, --tag, or --revision, Braid tracks the upstream repository's default branch.

Later, bring in upstream changes with:

braid update lib/grit

If you changed the vendored code locally and want to send those changes back upstream, inspect or save a patch:

braid diff lib/grit
braid diff lib/grit > grit.patch

If you have push access, commit your downstream changes first, then push the mirror changes back to the tracked branch or to a separate upstream branch:

braid push lib/grit
braid push lib/grit --branch myproject_customizations

For the common branch-mirror workflow, sync combines the tracked-branch push and follow-up update:

braid sync lib/grit

It pushes committed local mirror changes when the branch is still up to date, then updates the selected mirror so .braids.json records the new upstream revision. Use the explicit push and update commands when you need to push to a different branch or handle each step separately.

braid update lib/grit

Adding Mirrors

Add a whole upstream repository:

braid add https://github.com/rails/rails.git vendor/rails

Add only a subdirectory or a single file from upstream:

braid add https://github.com/twbs/bootstrap.git vendor/assets/bootstrap --path dist
braid add <upstream-git-url> licenses/PROJECT-LICENSE.txt --path LICENSE.txt

Track a specific branch or tag:

braid add https://github.com/rails/rails.git vendor/rails --branch 5-0-stable
braid add https://github.com/rails/rails.git vendor/rails-7 --tag v7.0.0

Lock a mirror to an explicit revision when you do not want ordinary braid update runs to move it:

braid add https://github.com/rails/rails.git vendor/rails --revision 5850a65

The local_path argument is optional. If you omit it, Braid derives the local path from the upstream repository name, or from the --path basename when mirroring a subdirectory or file, and places that derived path under your current directory.

Before adding, Braid checks any existing .braids.json and requires the target path to be available. Tracked, staged, unstaged, or untracked content at the target, under the target, or at a blocking ancestor stops the add before Braid fetches or writes mirror content.

Checking Status And Local Changes

Show every configured mirror, or just one mirror:

braid status
braid status vendor/rails

Status output includes the recorded revision and tracking mode, such as [BRANCH=main], [TAG=v1.0], or [REVISION LOCKED]. It also reports useful state markers:

  • (Remote Modified): the tracked branch or tag points at a different upstream revision.
  • (Locally Modified): the vendored files differ from the recorded upstream revision.
  • (Removed Locally): the configured mirror path is no longer present in the downstream repository.

Show local changes as a Git diff:

braid diff
braid diff vendor/rails
braid diff vendor/rails -- --stat
braid diff vendor/rails -- --cached

Arguments after -- are passed to git diff. This is useful for generating patches, limiting output, or checking staged changes only.

Updating Mirrors

Update one mirror to the newest revision for its tracked branch or tag:

braid update vendor/rails

Update every branch and tag mirror in lexicographic mirror path order:

braid update

Revision-locked mirrors are skipped by braid update without a path. When any are skipped, a successful no-path update prints:

Braid: skipped revision-locked mirrors:
  vendor/a
  vendor/z

Explicit-path updates do not print this skipped-mirror note. Strategy changes require a local path:

braid update vendor/rails --revision <revision>
braid update vendor/rails --branch main
braid update vendor/rails --tag <tag>

Before updating, Braid requires .braids.json and the mirror path being updated to be clean in both the index and working tree. For braid update without a path, that scoped cleanliness check covers every eligible branch or tag mirror before any mirror is fetched or updated.

If an update conflicts with local mirror changes, Braid leaves conflict markers in the mirror working tree, stages the updated .braids.json, and writes a prepared MERGE_MSG. Resolve the conflicts, then run the git add and git commit commands printed by Braid from the same directory where you invoked braid update. They use this shape:

git add -- ':(top)vendor/rails' ':(top).braids.json'
git commit -F '<MERGE_MSG path printed by Braid>'

If unrelated files were staged before the conflicted update, they remain staged and may be included in that manual commit unless you unstage them first. To abandon a conflicted update while preserving unrelated work, restore only the mirror path and .braids.json from HEAD, then remove .git/MERGE_MSG.

Syncing Mirrors

braid sync runs the safe push-then-update workflow for branch mirrors:

braid sync vendor/rails
braid sync vendor/rails vendor/rack

With no paths, braid sync selects every configured branch or tag mirror in lexicographic mirror path order and skips revision-locked mirrors, matching no-path braid update. When any revision-locked mirrors are skipped, successful no-path braid sync and braid sync --pull-only runs print:

Braid: skipped revision-locked mirrors:
  vendor/a
  vendor/z

Explicit paths are processed in the order provided, may name branch, tag, or revision mirrors, and do not print this skipped-mirror note.

Before any fetch, push, editor, worktree write, config write, or update commit, sync checks unresolved Git operation state, .braids.json, and every selected mirror path for index and working tree changes. Dirty mirrors outside an explicit selection do not block that explicit sync. Ignored-only files under a selected mirror do not block the default check.

Use --autostash when selected mirror paths have uncommitted work that should be carried across the sync:

braid sync --autostash vendor/rails
braid sync --pull-only --autostash vendor/rails

Autostash is path-scoped to the selected mirrors. It saves tracked staged changes, tracked unstaged changes, tracked deletions, untracked files, and ignored files under those selected mirror paths, then restores them after sync. Selected mirror-path index state is restored from the saved stash entry, while unrelated staged and unstaged files outside selected mirrors are left alone. Dirty .braids.json and unresolved Git operation state still stop before any autostash is created.

Autostash does not push uncommitted mirror changes. The push phase still uses only the mirror content recorded in downstream HEAD. When sync pushes a changed branch mirror, each upstream push uses the same commit-message review flow described for braid push, including optional generated-message prompts when configured.

If sync reaches a Braid update conflict after creating an autostash, Braid leaves the stash intact instead of applying it over conflict markers. Resolve the Braid update first using the printed conflict instructions, then follow the printed recovery command to apply the saved stash and restore selected-path index state. If automatic restoration succeeds but the saved stash cannot be dropped safely, Braid leaves your restored work in place, keeps the stash recoverable, and tells you to inspect git stash list before manual cleanup.

The default push phase only auto-pushes branch-tracking mirrors with committed local mirror changes. Branch mirrors without committed local changes are skipped quietly and still update normally, even if upstream has moved. Selected tag or revision mirrors with committed local changes stop the sync because sync has no --branch; run braid push <path> --branch <branch> for that explicit push intent, or rerun with --pull-only if you only intended to update.

If a changed branch mirror's upstream branch moved since the recorded revision, sync fails before any mirror is pushed. Update first, resolve conflicts if needed, commit, then rerun braid sync. If the selected mirror path itself was deleted from downstream HEAD, sync also fails rather than trying to push the deletion of the mirror root; deletions inside an existing mirror directory are ordinary local mirror changes.

sync pushes mirrors sequentially. If an earlier mirror push succeeds and a later mirror's generator or commit editor fails, the earlier upstream commit may already exist and the update phase is skipped. Rerun braid sync after resolving the failure to update downstream mirror revisions.

Use --pull-only to run only the update phase with the same scoped precheck:

braid sync --pull-only
braid sync --pull-only vendor/rails

Use --keep to retain temporary Braid remotes used during sync planning, push, and update:

braid sync vendor/rails --keep

Pushing Local Changes Upstream

braid push creates an upstream commit from the mirror content recorded in your downstream HEAD, opens Git's commit editor for the upstream commit message, and pushes that commit.

When available, the editor starts with commented guidance listing downstream commits that touched the mirror path since the last clean mirror state. The guidance includes full downstream commit messages so you can summarize, copy, or ignore the relevant context while writing the upstream commit message. Braid does not generate an upstream subject for you, and leaving the guidance in place does not add it to the final commit message.

This guidance is best-effort. If Braid cannot compute it safely, or if core.commentChar is set to auto, Braid prints a warning and opens the editor without the provenance block; the push still proceeds through the normal commit and push checks.

To prefill the editor with a generated draft message, set BRAID_PUSH_COMMIT_MESSAGE_COMMAND to a trusted local POSIX shell command. Empty or unset disables generation. Braid substitutes these placeholders with shell-quoted paths and leaves unknown placeholder-like text unchanged:

  • {REPO_DIR}: downstream repository root.
  • {CONTEXT_DIR}: temporary prompt context directory.
  • {PROMPT_FILE}: generated prompt file.
  • {MESSAGE_FILE}: file where the command must write the proposed message.

Example:

BRAID_PUSH_COMMIT_MESSAGE_COMMAND='codex exec -C {REPO_DIR} --add-dir {CONTEXT_DIR} --model gpt-5.5 -c '\''model_reasoning_effort="low"'\'' -o {MESSAGE_FILE} < {PROMPT_FILE}'

The command runs as /bin/sh -c from the downstream repository root with the current process environment. The prompt includes mirror metadata, downstream commit provenance when it can be collected, and the staged upstream diff. Diffs up to 5 KiB are included inline; larger diffs are written under {CONTEXT_DIR} and referenced from the prompt. The configured command is trusted local shell code and is not sandboxed by Braid. On Windows, configured generation is not supported; leave the environment variable unset or empty to use the normal editor flow.

When generation succeeds, Git's editor opens with the generated message followed by commented provenance guidance when available. The editor-reviewed content is the message Braid commits. If the generator exits nonzero, does not create the message file, or writes only whitespace, Braid opens the normal editor template with commented diagnostics and provenance guidance when available. Those comments are stripped from the final commit message if left in place.

For branch mirrors, pushing without --branch targets the tracked branch:

braid push vendor/rails

Use --branch to push to a different upstream branch, or when the mirror tracks a tag or fixed revision:

braid push vendor/rails --branch myproject_customizations

Braid stops without pushing if the upstream branch has moved since the recorded mirror revision. In that case, update the mirror first, resolve any conflicts, and then push.

Removing Mirrors

Remove a mirror from the downstream repository:

braid remove vendor/rails

Braid removes the vendored content, updates .braids.json, and creates a Braid: Remove mirror ... commit.

Before removing, Braid requires .braids.json and the mirror path to be clean in both the index and working tree. Local edits, local deletions, staged mirror changes, and untracked files under the mirror path stop the remove.

Remotes, Cache, And Paths

Braid normally creates Git remotes as needed and removes them when the command finishes. Use setup when you want Braid-managed remotes to exist in the repository, for example before inspecting them with Git:

braid setup
braid setup vendor/rails --force

The local cache is enabled by default. Without overrides, Braid stores it under the OS user cache directory with a braid child directory. Use BRAID_LOCAL_CACHE_DIR or --cache-dir to choose a location, and use BRAID_USE_LOCAL_CACHE=false or --no-cache to disable it.

Mirror paths stored in .braids.json always use repo-root-relative / separators, and ordinary Braid output uses those same repo-root-relative paths. CLI mirror path arguments may use / or \; Braid resolves them relative to the directory where the command was invoked, then normalizes them before config lookup or storage. Absolute local_path inputs are accepted only when they are inside the Git working tree, and stored config paths remain relative.

Commands without a local_path, such as braid status, braid diff, braid setup, braid update, and braid sync, operate on the repository-wide mirror set from any subdirectory. Relative --cache-dir values and BRAID_LOCAL_CACHE_DIR values remain relative to the process directory. Git diff arguments after braid diff ... -- are passed through as raw git diff arguments from the process directory; Braid only anchors its own internal mirror pathspecs.

The --path option is an upstream Git path and should use Git's / separator. When adding from a local Windows repository path such as C:\src\upstream.git, Braid keeps that original upstream value for Git and derives the default mirror path from the repository basename.

Braid validates configured mirror paths for cross-platform safety. It does not preflight every file inside the selected upstream tree; if an upstream filename cannot be materialized on the current OS, Git reports the checkout failure.

When you vendor only a subdirectory or single file, remember that license files outside the mirrored path are not copied automatically. If the upstream license must travel with the vendored content, add it separately or mirror a path that includes it.

Command Reference

Command Purpose
add Add a branch, tag, or revision mirror and create the initial Braid commit.
status Show whether mirrors have remote, local, or removal changes.
diff Show local mirror changes, with Git diff arguments after --.
update Update one mirror, or every branch/tag mirror when no path is given.
push Push committed local mirror changes upstream.
sync [local_path...] [--pull-only] [--autostash] [--keep] Push changed branch mirrors, then update selected mirrors.
remove Remove mirrored content and config.
setup Add or refresh Braid-managed Git remotes.
version Print the Braid version.

Build And Test

This repository is Bazel-first. Use Bazel as the source of truth for builds, tests, formatting, vetting, linting, and Go toolchain selection.

bazel test //...
bazel build //cmd/braid:braid

Fast Go quality checks used by CI run through the Bazel-pinned Go SDK:

bazel run @rules_go//go -- fmt ./...
bazel test //...
bazel run @rules_go//go -- vet ./...
bazel run @rules_go//go -- run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.0 run

GitHub Actions CI Workflow

GitHub Actions CI workflow lives in .github/workflows/ci.yml and has two job families:

  • Go quality and lint runs formatting, tests, vet, and golangci-lint through Bazel. Tests use bazel test //... so unit tests, real-Git tests, and the executable integration target all run as first-class Bazel targets.
  • Integration (<platform>) runs the executable integration target on the non-default native release platforms used for early cross-platform signal.

Each job installs Bazel, then uses rules_go to supply Go. golangci-lint is run with bazel run @rules_go//go -- run ... so CI still has a single automation entrypoint: Bazel.

Release Builds

Releases are cut through GitHub Actions:

  1. Run the Release Cut workflow from main with a stable version such as 0.1.0 or v0.1.0.
  2. The workflow validates the exact main commit, creates an annotated vX.Y.Z tag, and dispatches the Release workflow on that tag.
  3. The Release workflow builds the supported native artifacts, verifies checksums, and creates a draft GitHub release.
  4. Review the draft release assets and generated notes, then publish manually.

The workflow files own the operational details, including runner labels, permissions, version stamping, artifact names, and verification commands.

About

Simple tool to help track git vendor branches in a git repository.

Resources

Contributing

Stars

Watchers

Forks

Contributors