Design
The other documentation chapters explain what git-splice does and how
to use it: getting started,
concepts,
workflows,
comparisons with Git's tools,
third-party tools and
other VCS tools,
the command reference and
scenarios. Design explains why: the principles, tradeoffs
and constraints behind that behaviour. Mechanisms appear here where they
explain a choice; behaviour and usage belong in the relevant chapters.
These principles also guide future features. Contributors, including AI agents, should use them to evaluate proposed behaviour, not just to describe the current implementation. A change that needs a different tradeoff should explicitly reconsider the affected principle and its rationale.
Principles
- Keep each repository's history relevant to its users. Importing a library does not require importing its entire history into the monorepo; publishing a library change does not require exposing the monorepo's unrelated work. This is why changes cross the splice while the histories stay separate. A folder's earlier history may never have been intended for that upstream, so publication must follow the splice's identity rather than include everything that once occupied its path.
- Store as little state as possible. Redundant records can disagree,
and every update becomes another opportunity to leave them inconsistent.
Derive what existing history, configuration and file locations already
tell us instead of storing it again. Redundant bookkeeping in
git-subrepomotivated this principle (state rationale). - Nesting should not change a splice's meaning. Given equivalent
history, upstream state and settings, syncing
vendor/a/b/should produce the same result for that splice as syncingb/in a standalone clone ofvendor/a's repository. A project should not need a different dependency workflow because it is embedded in another repository. This requires portable state and defaults and recovering inner history hidden by an enclosing squash (scenario). It's also why the rebuild of a splice records each splice inside it at that splice's own rebuild, the commit its upstream gets with the same push: the enclosing upstream then records what the inner one has, and a clone of it rebuilds the inner splice as the monorepo does (scenario). - Rebuild deterministically. The same committed history produces the same upstream commits, so a later push can fast-forward without a monorepo commit recording the earlier push. Using original metadata rather than the current time or user, and leaving rebuilt commits unsigned, avoids inputs that would change their IDs between runs (sync model).
- Keep repeated syncs scalable. An advancing sync point lets rebuilds
skip history already accounted for, rather than revisit an ever-growing
history on every sync.
git subtree split --squashdemonstrates that scaling problem. Advancing the sync point costs a recording commit; an explicit bump lets the user choose that tradeoff rather than impose it on every push. - Preserving monorepo commit topology is a non-goal. First-parent
ancestry is enough for current use cases; the original history remains
available in the monorepo. The lesson from
git subtreeand Josh was that equivalent history mapping adds complexity with little benefit. More faithful topology can be reconsidered when a use case justifies it. - Inspection must predict publication. Different interpretations of local changes could make inspection promise something a push does not send. Comparing the same rebuilt history makes inspection a useful preview of publication (sync model).
- Ask “what would Git do?” Git has much more experience solving version-control problems. Where its solution works well, implement the closest equivalent: local branch-tracking settings and reflogs are examples. Problems and bugs caused by a Git design are reasons not to adopt it: examples are the global tag namespace and Git submodules (upstream choices, submodule comparison).
- Git's words keep Git's meaning. Users bring expectations from Git, so an existing Git term must name an equivalent operation or concept. If the meaning differs, invent a word Git has not taken, as with splice.
- Take advantage of Git's full power. Git already provides flexible
tools to shape history, such as rebasing
and editing rebuilt commits in a worktree.
Reimplementing those workflows would duplicate a problem Git solves
better and unnecessarily restrict the user's choices. History editing
stays with Git;
git-splice's contribution is to streamline work across multiple repositories from one monorepo. Prefer composing ordinary Git operations with splice commands over adding another history editor. - Use the user's Git so repository operations remain observable.
Running the installed
gituses the user's version and lets them follow and log the commands executed on their machine. Replacing it with a library such as gitoxide would substitute another implementation and lose that direct Git command trace. Using Git also avoids maintaining another implementation of repository operations (tracing scenario). - Make information easy to discover and side effects deliberate. Requiring users to enumerate splices before inspecting them can hide the very changes they need to discover. Applying that same broad default to writes risks changing or publishing unrelated work. This difference in the cost of a wrong scope motivates broad inspection and explicit write targets.
- Design for pull requests across repositories. A monorepo feature branch represents work to submit upstream, so matching branch names connect its changes to the corresponding upstream PRs. The upstream default branch supplies the comparison target before a feature branch exists; create that branch only when there are commits to submit (branch rationale).
- Refuse rather than guess. Ambiguous history, targets or inputs need an explicit decision. Guessing can discard changes or publish to the wrong place; refusing with an explanation lets the user resolve the ambiguity before anything changes.
- Weigh every fix against its complexity. A fix can introduce more failure modes than it removes. Prefer simple, safe fixes; weigh the benefit and frequency of a case against the complexity it adds to every future change. Rare cases can be documented or tracked as edge cases instead of complicating the ordinary workflow.
Architecture
Why the name "splice" introduces the terminology, including “splicing in” and “splicing out”, and the naming motivations. The following chapters develop the rationale for state and upstreams. Going forward explores potential features and open questions, including directions that are out of scope; these proposals do not describe released behaviour or promise an implementation.