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.
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.
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/braidFor local development, run the binary through Bazel:
bazel run //cmd/braid:braid -- versionBraid 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
- Quick start
- Adding mirrors
- Checking status and local changes
- Updating mirrors
- Syncing mirrors
- Pushing local changes upstream
- Removing mirrors
- Remotes, cache, and paths
- Command reference
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 --helpStart 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/gritBraid 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/gritIf 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.patchIf 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_customizationsFor the common branch-mirror workflow, sync combines the tracked-branch push
and follow-up update:
braid sync lib/gritIt 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/gritAdd a whole upstream repository:
braid add https://github.com/rails/rails.git vendor/railsAdd 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.txtTrack 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.0Lock 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 5850a65The 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.
Show every configured mirror, or just one mirror:
braid status
braid status vendor/railsStatus 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 -- --cachedArguments after -- are passed to git diff. This is useful for generating
patches, limiting output, or checking staged changes only.
Update one mirror to the newest revision for its tracked branch or tag:
braid update vendor/railsUpdate every branch and tag mirror in lexicographic mirror path order:
braid updateRevision-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.
braid sync runs the safe push-then-update workflow for branch mirrors:
braid sync vendor/rails
braid sync vendor/rails vendor/rackWith 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/railsAutostash 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/railsUse --keep to retain temporary Braid remotes used during sync planning, push,
and update:
braid sync vendor/rails --keepbraid 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/railsUse --branch to push to a different upstream branch, or when the mirror tracks
a tag or fixed revision:
braid push vendor/rails --branch myproject_customizationsBraid 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.
Remove a mirror from the downstream repository:
braid remove vendor/railsBraid 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.
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 --forceThe 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 | 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. |
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:braidFast 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 runGitHub Actions CI workflow lives in .github/workflows/ci.yml and has two job families:
Go quality and lintruns formatting, tests, vet, and golangci-lint through Bazel. Tests usebazel 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.
Releases are cut through GitHub Actions:
- Run the
Release Cutworkflow frommainwith a stable version such as0.1.0orv0.1.0. - The workflow validates the exact
maincommit, creates an annotatedvX.Y.Ztag, and dispatches theReleaseworkflow on that tag. - The
Releaseworkflow builds the supported native artifacts, verifies checksums, and creates a draft GitHub release. - 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.