tangled #
Git import and push against tangled knots (VISION D20). importRepo and pushChanges come from @nibling/sdk/git, not the package root, so isomorphic-git (about 70 KB gzipped) loads only where one of them is imported. The app imports it dynamically.
import { resolveRepo } from '@nibling/sdk';
const { importRepo, pushChanges } = await import('@nibling/sdk/git');
const repo = await resolveRepo('tangled.org/knot-docker');
const { commit, ref, files } = await importRepo(repo);
await pushChanges({
repo,
base: { commit, ref },
files: new Map([['readme.md', bytes]]),
removed: ['old.txt'],
message: 'Edit from nibling',
author: { name, email },
token: () => pushToken(session, repo.knot),
});
importRepo returns regular files under NFC paths, the spelling project paths use. Symlinks, submodules, names that D18 path rules reject (such as one with a backslash), and names that collide after NFC are left out and listed in skipped. It fails with too-large past maxFiles (10,000) or maxBytes (100 MB), counted both on the wire and in file bytes. options.ref fetches that branch instead of the remote's default; the app's rerootTree (packages/app/src/platform/base.ts) then restricts and re-roots the result to a subdirectory when the URL named one (D18's base.path) — importRepo itself always returns the whole tree at that ref.
pushChanges takes an overlay relative to base, the same shape as a D18 record: files adds or replaces paths and keeps an existing file's mode, and removed deletes base paths. It fetches the branch tip at depth 1 and fails with remote-moved if the tip is no longer base.commit. Pass token as a function so the 60-second token is minted after that fetch, when the knot asks for auth. Overlay paths are NFC; an edit lands on the name git already has, keeping its mode, and a file written over a symlink becomes a regular file. pushToken and pushChanges refuse a knot that is not https with insecure-knot, except plain http to loopback. isomorphic-git cannot push a tree holding a name it deems unsafe, such as one with a backslash, so such a push fails with bad-change.
Observed on knot1.tangled.sh (2026-11-06) #
- A repo DID's document has
{ "id": "#tangled_knot", "type": "TangledKnot", "serviceEndpoint": "https://knot1.tangled.sh" }, plus an#atproto_pdsentry pointing at the same knot. org.tangled.knot.describeKnotreturns"did": "did:web:knot1.tangled.sh", and/.well-known/did.jsonserves that document.pushTokenderives the same value from the host.info/refs,git-upload-pack, thegit-receive-packpreflight, and the 401 frominfo/refs?service=git-receive-packall sendaccess-control-allow-origin: *and allowAuthorization, Content-Type, atproto-proxy.- The knot ignores the Basic username. It parses the password, or a Bearer token, as a JWT.
- Both
https://<knot>/<repo did>andhttps://<knot>/<owner did>/<name>serveinfo/refs.
tangled's own URL shapes (verified 2026-11-06) #
Against https://tangled.org/tangled.org/core and https://tangled.org/nibling.run/core: a directory is /<owner>/<name>/tree/<ref>(/<path>), a file is /<owner>/<name>/blob/<ref>/<path>, a commit is /<owner>/<name>/commit/<sha>, and the commit log is /<owner>/<name>/commits/<ref>. A branch whose own name holds a / is escaped as %2F in its tree/blob segment (seen as /tree/op%2Fvlpyxsyslytq and /tree/nmoo%2Fapp%2Fat-uri), so the ref is always exactly one URL segment and whatever follows it, split on a literal /, is the path — never ambiguous, unlike GitHub's heuristic. packages/app/src/pages/route.ts's parseRepoPath and repoHref read and write this shape for tree and blob; a commit or the commit log is not read yet and just opens the repo at its default branch.
Browser requirement #
isomorphic-git 1.44 uses a global Buffer and throws Missing Buffer dependency without one, so git.ts installs the buffer package's when there is none.
Unverified #
- A real push to a knot. Tests push to a local
git http-backend. - That a PDS grants
getServiceAuthforlxm=org.tangled.git.receivePackunder the OAuth scoperpc:org.tangled.git.receivePack?aud=*. - Whether the 401 with
www-authenticate: Basicmakes any browser show a credentials prompt for a cross-originfetch.