Package managers
git-splice isn't meant to become a package manager. But since splices can
nest, they form a dependency graph, and a package manager like npm, Yarn
or Cargo has solved many of the same problems. This page collects what
maps, and what doesn't.
What maps
| Package manager | git-splice |
|---|---|
| package name | upstream (upstreams) |
what you ask for: a version range or tag, e.g. ^1.2 (a descriptor in Yarn) | the upstream branch a splice syncs with |
| what you got: the exact version in the lockfile (a locator in Yarn) | the synced commit U(H) |
| a dependency declaring its own dependencies | a nested .splice, which travels with the upstream above (nesting) |
| registries and mirror servers | several upstreams per splice (several-upstreams) |
| two versions of one package in the tree | independent splices of one upstream, at two versions (upstreams) |
| one package, deduplicated | mirrors: splices with the same id (mirrors) |
yarn dedupe --check | a report of mirrors that drifted, not yet |
| — | the id: which copies are one splice, history included. A package can't be edited in place, so its name and version say that already |
A package name is one identity in a registry. A URL is only a location: two URLs of one repository are two upstreams to the monorepo (upstreams).
What doesn't: reference versus containment
A package manager refers to packages. It builds a graph keyed by name and version, and installs each package once, wherever it's needed:
- Diamonds get one copy, if the version ranges allow it.
- Cycles are allowed in npm, since installing a package that's
already installed is a no-op. Node finds a package by walking up the
node_modulesfolders, so a dependency that a folder above provides needs no copy of its own.
A splice contains its files, and a nested splice is part of the files of the splice above. So:
- Diamonds get a copy per path, which can be at different versions (diamond).
- Cycles never end, and aren't supported, though not prevented yet (cycle).
Containment is the point of git-splice, not an accident: a splice's
upstream is complete on its own, and anyone can clone it without git
splice. And you can change a splice in place, and send the change back
with push. A package manager can only patch a package (yarn patch,
patch-package) or link a local copy (link:, portal:).
What could be learned
- A drift report, as
yarn dedupe --checkreports packages that could share a version: mirrors whose files differ (mirrors). - Skipping a cycle instead of refusing it, as Node does: a splice that's already above needs no copy below. It would need a reference where today there is content.
Zero installs
Yarn's zero installs commit the package cache, so a fresh clone runs without an install step. A splice has that already: its files are committed in the monorepo, and a clone of the monorepo has them, with no fetch (splice-refs).
The difference is what's committed. Yarn commits one compressed archive per package version, deduplicated across the graph, plus a map of where each one is used. A splice commits plain source files, once per copy. Git stores identical files once, but the checkout and every diff has each copy. And a package is usually built before it's published, e.g. from TypeScript, while a splice has the upstream's sources, so it is built as part of the monorepo.
Explicit branches
What a splice asks for is implicit today: the upstream branch named like
the monorepo's current branch (design).
A package manager writes it down, as a range in package.json. A splice
could do the same, with a branch in .splice or a --branch per
command.
That would help Jujutsu (jj) in
particular. In a colocated repository, jj leaves Git's HEAD detached,
and has bookmarks instead of a current branch. git-splice stops on a
detached HEAD, since it has no branch name to follow
(#41). An explicit
branch would make the current branch only a default.