Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@

Manage embedded git repositories (anonymous gitlinks) without `.gitmodules`. Provides hooks that restore standard git-command ergonomics for embedded child repos while keeping the child's origin URL out of the public parent repo.

## ✨ What's New

### Latest: v1.1.12 (October 2026)

- **Header tooling on fix-headers 2.1.4** β€” the `@cldmv/fix-headers` dev dependency moves to 2.1.4 and the header pass was re-run; every file already matched, so nothing was restamped. No CLI code, hook, published file or runtime dependency changed (#91).
- **Complete version history** β€” every release from v1.0.0 onward now has a changelog under [docs/changelog/](https://github.com/CLDMV/git-embedded/tree/master/docs/changelog/). Note that v1.1.3 raised `engines.node` to `>=22.12.0` despite being a patch release, and that v1.1.0 (first published as part of v1.1.1) added the `pre-push` check and changed the default `reference-transaction` guard.
- [View full v1.1.12 Changelog](https://github.com/CLDMV/git-embedded/blob/master/docs/changelog/v1/v1.1.12.md)

### Recent Releases

- **v1.1.11** (October 2026) β€” the CI `βœ… Required PR Check` mirror job runs on every path instead of being skipped on in-repo PRs, plus lockfile updates (#85, #86, #88) ([Changelog](https://github.com/CLDMV/git-embedded/blob/master/docs/changelog/v1/v1.1.11.md))
- **v1.1.10** (October 2026) β€” uniform file headers from the shared CLDMV config, the verbatim Apache-2.0 license text, a v4.29.2 workflow sync and a batch of dependency updates (#76, #79, #80, #82, #83, #84) ([Changelog](https://github.com/CLDMV/git-embedded/blob/master/docs/changelog/v1/v1.1.10.md))
- **v1.1.9** (September 2026) β€” the canonical CLDMV ESLint/Prettier config with `.jsonv` support, the vitest 5 Node range in CI, and signed redirected security PRs (#48) ([Changelog](https://github.com/CLDMV/git-embedded/blob/master/docs/changelog/v1/v1.1.9.md))
- **v1.1.8** (September 2026) β€” `vitest` and `@vitest/coverage-v8` move to 5.x together (#70) ([Changelog](https://github.com/CLDMV/git-embedded/blob/master/docs/changelog/v1/v1.1.8.md))

πŸ“š **For complete version history, see [docs/changelog/](https://github.com/CLDMV/git-embedded/tree/master/docs/changelog/) and the [GitHub Releases](https://github.com/CLDMV/git-embedded/releases).**

## What this is

Git uses **gitlinks** internally to track sub-repositories: a tree entry of mode `160000` pointing at a specific commit SHA in another repository. Submodules are built on top of gitlinks, with a registry file (`.gitmodules`) that records the child's URL alongside the gitlink. The URL is what makes `git clone --recurse-submodules`, `git submodule update`, and `submodule.recurse=true` checkout-flavored automation work β€” but it's also what publicly advertises the child repo's existence and location.
Expand Down
60 changes: 60 additions & 0 deletions docs/changelog/v1/v1.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# @cldmv/git-embedded v1.0.0 Changelog

**Release Date**: May 2026
**Release Type**: Major (initial release)
**Availability**: Published to npm ([@cldmv/git-embedded@1.0.0](https://www.npmjs.com/package/@cldmv/git-embedded/v/1.0.0)); the repository has no `v1.0.0` tag

---

## Overview

Initial release of `@cldmv/git-embedded`, a CLI and hook set for managing embedded git repositories (anonymous gitlinks, tree entries of mode `160000`) without a `.gitmodules` registry. The package restores the working-tree ergonomics that submodule registration normally provides, while keeping the child repository's origin URL out of the public parent repository.

The motivating use case is an open-source repository that runs a comprehensive test suite in CI but keeps that suite in a private child repo. The mechanism is general: the hooks work for any gitlink, such as private vendor directories or license-restricted dependencies.

---

## ✨ Features

### Hooks

- **`reference-transaction`** blocks HEAD-moving operations (`git checkout`, `git switch`, `git reset`, `git pull`, `git merge`, `git rebase`, `git bisect`, `git cherry-pick`) when any embedded child repo has uncommitted changes, so the parent never moves to a new commit while a child stays behind, dirty. It requires git 2.28 or newer.
- **`update-embedded-repos`** is installed as `post-checkout`, `post-merge` and `post-rewrite`. After a HEAD-moving operation it walks every gitlink in the new HEAD and updates the embedded child to its pinned SHA, fetching missing commits from the child's own `origin` remote.

### CLI (`git embedded …`)

- `doctor` inspects the environment and reports what an install would do; it takes no action.
- `install-hooks` installs the per-repo hooks into `.git/hooks`, adapting to the existing setup. With nothing configured it offers to create a dispatcher at `~/.config/git/hooks/_dispatch`, link every standard hook name to it and set the global `core.hooksPath`; with a canonical dispatcher already present it installs only the per-repo scripts; with a dispatcher missing entries it offers to add the missing symlinks. It refuses to install over Husky, lefthook, simple-git-hooks or pre-commit, and over a non-conforming dispatcher or bare `.githooks/`, and prints hand-integration instructions instead.
- `uninstall-hooks` removes only hooks recognizably installed by this CLI.
- `init` runs `install-hooks` and then sets `advice.addEmbeddedRepo=false` to silence git's "embedded git repository" warning.
- `link <local-path> <remote-url>` clones a repo and stages it as an anonymous gitlink, without committing and without writing `.gitmodules`.
- `install-template` installs the hooks into `init.templateDir/hooks` so new repositories start with them wired.
- `print-hook-script <name>` writes a packaged hook script to stdout.
- `version` (aliases `-V`, `--version`) prints the package, Node and platform versions.
- `install-hooks` accepts `--no-symlinks` (hard links instead of symbolic links, which avoids the Windows UAC prompt on a single volume), `--yes` and `--dispatcher-dir <path>`.

### Platform and requirements

- Node 20.19 or newer for the CLI; the hooks themselves are shell scripts with no Node dependency at hook time.
- Linux, macOS and Windows. On Windows, symlink creation requests a one-shot elevation unless `--no-symlinks` is used.

---

## πŸ“š Documentation

- README covering the gitlink model, installation, usage, manual install and compatibility.
- `docs/design.md` describing how the hooks work, the coverage matrix, limitations and the comparison with standard submodules.
- `docs/use-case-private-tests.md` describing the private-tests motivation, threat model and licensing strategy.

---

## πŸ”§ Dependencies

Runtime dependencies: `@cldmv/slothlet` `^3.7.0`, `@cldmv/wisp` `^1.0.1`, `chalk` `^5.4.1`, `commander` `^14.0.0`, `marked` `^15.0.12` and `marked-terminal` `^7.3.0`. Development tooling: `vitest` `^2.1.9` with `@vitest/coverage-v8`, `eslint` `^9.18.0` and `prettier` `^3.4.2`.

---

## Upgrade notes

- First release; nothing to migrate.
- Next release: [v1.1.0](./v1.1.0.md).
112 changes: 112 additions & 0 deletions docs/changelog/v1/v1.1.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# @cldmv/git-embedded v1.1.0 Changelog

**Release Date**: July 2026
**Release Type**: Minor
**Availability**: Release commit on `master` only; not tagged and not published to npm

---

## Overview

Version 1.1.0 turns git-embedded from a hook installer into a full workflow for embedded children. It adds commands to restore children on a fresh clone (`restore`), share their URLs between machines (`record`, `export`), and move them to new pins after a pull (`sync`). The `reference-transaction` guard gains configurable modes, and a new `pre-push` hook refuses to publish a parent whose pins point at child commits that were never pushed.

Runtime code changed substantially. Two defaults change for existing installs, so read [Breaking Changes](#-breaking-changes) before upgrading, even though this is a minor release. This version was never published to npm; users on npm move from [v1.0.0](./v1.0.0.md) directly to [v1.1.1](./v1.1.1.md), which includes everything here.

---

## πŸ’₯ Breaking Changes

### A new `pre-push` hook rejects pushes with unpublished pins

`install-hooks` and `install-template` now install a `pre-push` hook alongside the existing four (`PACKAGE_HOOK_MAP` gains `pre-push`; `print-hook-script` accepts it). With the default `embedded.pushRecurse=check`, a push is rejected when a gitlink pin that is new to the remote is not reachable from any `origin` branch of the child, or when the child repo is not present locally to verify against. A push that worked on v1.0.0 can therefore fail on v1.1.0 once the hook is installed.

Upgrade steps:

- Hooks are copied into a repo's `.git/hooks`, so existing repos only pick this up when `git embedded install-hooks` is run again.
- Push the child first when the rejection says to, or set `git config embedded.pushRecurse on-demand` to have the hook try pushing the child's current branch before verifying.
- Set `embedded.pushRecurse=off` to disable the check, or bypass once with `git -c embedded.pushRecurse=off push`.

### The `reference-transaction` guard defaults to `precise`, not "block on any dirty child"

On v1.0.0 any uncommitted change in any child blocked every HEAD move in the parent. The guard now reads `embedded.guard` (`precise`, `strict` or `off`, default `precise`). In `precise` mode only a dirty child whose HEAD differs from the pin in the new commit blocks; clean children, and dirty children whose pin already equals their HEAD, no longer block. That is more permissive than v1.0.0 by default.

Upgrade steps:

- To keep the v1.0.0 "dirty child blocks everything" behavior, and additionally refuse a parent commit while any child's pin is stale, set `git config embedded.guard strict`.
- `strict` blocks any dirty child on any move, and on commit, merge and cherry-pick requires each child's HEAD to equal its recorded pin. Checkout and reset only require all children clean.
- `embedded.guard=off` disables guarding; `git -c embedded.guard=<mode> <command>` overrides once.
- Because the hook script is copied into `.git/hooks`, re-run `git embedded install-hooks` to receive the new guard.

### `link` accepts empty directories and now refuses targets outside the worktree

`git embedded link <local-path> <remote-url>` previously refused any existing path. It now clones into a missing or empty real directory (a fresh clone of a parent leaves each gitlink as an empty directory), and still refuses a non-empty directory, a file, an unreadable path or a symlink (even to an empty directory). It also exits with status 2 when the target resolves outside the repository worktree, and records the child under the normalized repo-relative path (`./tests` and `tests/` both record as `tests`).

---

## ✨ Features

### Restoring children on a fresh clone: `git embedded restore`

A fresh clone of the parent materializes each embedded child as an empty directory, because the parent commits a path and a pinned SHA but never a URL. `restore` clones each child and checks out its pinned SHA. It accepts `[paths...]`, `--from <manifest>`, `--base <url-base>`, `--skip <paths>` and `--dry-run`.

Each child's clone URL is resolved from up to four optional sources, strictest first:

1. The local config registry, `embedded.<path>.url` in the clone's `.git/config` (never committed).
2. A manifest JSON file passed with `--from`.
3. `--base <url-base>`, which derives `<url-base>/<basename>.git`.
4. Convention: the parent's origin with its last path segment replaced by `<basename>.git`, handling both URL-style and scp-style origins.

Every clone is SHA-verified. If the pinned commit is absent after a fetch (for example a convention guess named the wrong repo), the clone that `restore` created is removed and the child is reported `pinned-mismatch`, so a wrong guess never leaves the wrong code in place. Per-child outcomes are `restored`, `already-present`, `unresolved`, `pinned-mismatch` and `skipped`; the command exits non-zero if any child is `unresolved` or `pinned-mismatch`.

Branches are resolved with the same layering (`embedded.<path>.branch`, then the manifest); when neither provides one and exactly one `origin` branch contains the pin, that branch is used. The child then ends on the branch at the pin with upstream tracking set to `origin/<branch>`. An ambiguous or unmatched pin keeps a detached checkout. A successful restore registers the URL and branch locally.

### Sharing URLs between machines: `record` and `export`

- `git embedded record [paths...]` writes the origin URL and current branch of every child present on disk into the local registry.
- `git embedded export [-o <file>] [--scan]` serializes the registry to a manifest JSON on stdout or a file; `--scan` records present children first. When `-o` writes inside the worktree the filename is appended to `.git/info/exclude`. The manifest contains the child URLs the design keeps out of the tree and is meant to be carried out-of-band, never committed.

### Day-2 syncing: `git embedded sync`

After the parent pulls commits that move gitlink pins, `sync [paths...] [--skip <paths>] [--dry-run]` moves the children already on disk to the new pins; it never touches the parent. A clean child follows the pin: a child on its registered branch fast-forwards, and a detached child snaps to the pin. Work in progress is reported and left alone: `dirty` (uncommitted changes), `ahead` (commits beyond the pin) and `unregistered-branch`. A pin missing locally triggers one `git fetch origin` in the child; if it is still absent the child is `pin-unavailable` and the command exits non-zero, as does an unexpected checkout failure (`sync-failed`).

### Guard configuration

- `embedded.guard` (`precise`, `strict`, `off`) controls when the `reference-transaction` hook blocks a HEAD move; see Breaking Changes for the semantics.
- `embedded.pushRecurse` (`check`, `on-demand`, `off`) controls the `pre-push` verification. The hook is git-embedded's analog of `git push --recurse-submodules=check`, which stock git cannot provide for children not registered in `.gitmodules`.
- `link` now records the child's URL and branch into the local registry after staging.

---

## πŸ› Bug Fixes

- The `reference-transaction` guard now watches the current branch ref (`refs/heads/<branch>`) as well as `HEAD`. Git 2.54 reports a plain commit only on the branch ref, so on that version commits slipped past a guard that matched `HEAD` alone.
- The guard no longer misreads a child it cannot read: an unborn child (fresh `git init`) is skipped, and in `strict` mode a broken or corrupt child fails closed. Git commands run inside a child are anchored with `GIT_CEILING_DIRECTORIES` so a broken child cannot silently resolve the parent's HEAD, and child paths containing spaces are handled.
- `link` passes `--` before the URL and path to `git clone` and `git add`, so a value starting with `-` cannot be interpreted as a git option (such as `--upload-pack`).

---

## πŸ“š Documentation

- README documents the guard configuration table, `restore`, branch-aware checkout, obscured children, `record` / `export`, and `sync`.
- `docs/design.md` is expanded to cover the guard modes, the push check, the registry and the restore/sync model.

---

## πŸ”§ CI & tooling

- The v4 workflow set from `CLDMV/.github` is added: CI, CodeQL, dependency review, Dependabot configuration and auto-merge, feature-PR and release automation, hotfix redirection, branch retention, labeler, stale, welcome, tag health, master-commit audit, publish and bootstrap.
- New test suites cover the provisioning commands (`tests/embedded-provisioning.test.mjs`) and the hook guards (`tests/hook-guards.test.mjs`).
- `tmp/` is added to `.gitignore`.

## πŸ”§ Dependencies

- Dev dependencies `vitest` and `@vitest/coverage-v8` move from `^2.1.9` to `^4.1.10`. Runtime dependencies are unchanged.

---

## Upgrade notes

- This version was not published to npm; install [v1.1.1](./v1.1.1.md) or later from the registry.
- Re-run `git embedded install-hooks` in each parent repo to install the new `pre-push` hook and the updated `reference-transaction` guard.
- Set `embedded.guard=strict` if you rely on the v1.0.0 behavior of blocking on any dirty child.
- No runtime dependency or `engines` changes.
35 changes: 35 additions & 0 deletions docs/changelog/v1/v1.1.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# @cldmv/git-embedded v1.1.1 Changelog

**Release Date**: July 2026
**Release Type**: Patch

---

## Overview

A tooling release that adds the missing `build:ci` script and moves the test suite onto `@cldmv/vitest-runner`. No runtime behavior changed: the only edits under `src/` are coverage-ignore comments, plus a one-line `/* v8 ignore else */` restructuring in the help renderer that does not change its output.

This is the first version of the [v1.1.0](./v1.1.0.md) feature set published to npm, so it carries everything described there. Users upgrading from the published v1.0.0 should read that changelog's Breaking Changes first.

---

## πŸ”§ CI & tooling

- `package.json` gains `build:ci` (a no-op echo, since the package ships source) and `ci:coverage`, which runs `npm run coverage`. The shared CI workflows expect both scripts to exist ([#15](https://github.com/CLDMV/git-embedded/pull/15)).
- `test` and `coverage` now run `node tests/run-vitest.mjs`, a thin wrapper around `@cldmv/vitest-runner` that runs each test file in its own process and, under coverage, merges per-file blobs so one process never holds the whole suite's coverage data. `coverage` passes `--coverage-quiet`; `test:watch` still calls vitest directly.
- `.configs/vitest.config.mjs` inlines `@cldmv/slothlet` via `server.deps.inline` so the coverage collector attributes execution of the slothlet-loaded API modules, and excludes the Windows-only elevation helpers (`src/api/link/elevate-windows.mjs`, `src/lib/elevate-windows-child.mjs`) from coverage.
- About 5,000 lines of new tests (CLI, hooks, detectors, link, restore/sync, help rendering) raise coverage of `src/`; the `v8 ignore` comments added under `src/` mark defensive branches the suite cannot reproduce.
- CI skips the type-check step with a recorded reason: the API is composed dynamically by slothlet and cannot be statically typed.
- Added the `cla.yml`, `release-notify.yml` and `scorecard.yml` workflows, and refreshed the headers of the existing workflow files.

## πŸ”§ Dependencies

- **NEW** dev dependency: `@cldmv/vitest-runner` `^1.2.0`.
- Runtime dependencies are unchanged.

---

## Upgrade notes

- No runtime changes; no action required beyond the notes in [v1.1.0](./v1.1.0.md) if upgrading from v1.0.0.
- Previous: [v1.1.0](./v1.1.0.md). Next: [v1.1.2](./v1.1.2.md).
Loading
Loading