Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Roadmap: order the LFS slices and drop the stale Remaining block

671f2aa75e988725c1bfeea1279526dd01a8a2b6 · Keenan Nemetz · 16d ago · parent 65b88b3b

Roadmap: order the LFS slices and drop the stale Remaining block

Slice 5 (locking) had landed after slice 6, and the "Remaining" heading still
held a parenthetical about artifact uploads that shipped in Phase 7 slice 6.

docs/roadmap.md +39−44

@@ -647,11 +647,46 @@ reachable from every ref in every repository — plus archived logs, which no re
647 647 — and removes the rest. Anything younger than the grace period (default 1h) is kept,
648 648 because an upload that has not been pushed yet is not garbage.
649 649
650 Remaining:
650 ✅ **Slice 5 — file locking.** `git lfs lock <path>` works over the SSH transfer, so a
651 binary that git cannot merge can be reserved by one person before they edit it: a push
652 that changes a locked path is refused, naming the holder, and the lock is released when
653 its holder says so. This is what makes a repository of design files usable by more than
654 one person, and it is the reason a studio would pick a forge with it over one without.
655
656 The HTTP locking API is the usual path for git-lfs, and it is a *write* surface — which
657 this instance does not have, because its web is anonymous and read-only. So locking rides
658 the SSH transfer protocol instead (`lock`, `list-lock`, `unlock`, advertised as the
659 `locking` capability), which means the key that pushed is the key that locks: no second
660 endpoint, no token, nothing new to authenticate. Locks live in `data/users.db` beside the
661 roster and the access rules — the file that holds what git cannot rebuild — because a lock
662 resurrected from history would be exactly wrong: the point of a lock is that it is
663 released. They are keyed by account id, so a rename changes nothing.
664
665 The interesting decision is *where the refusal lives*. git-lfs enforces locks on the
666 client: it asks the server what is held and declines to send the push. That is a courtesy,
667 and a plain `git push` walks straight past it — so the rule also sits in `pre-receive`, at
668 the one place this forge enforces everything else, where it cannot be turned off by a
669 client setting. What it guards is narrow on purpose: only the paths the push actually
670 writes, so reading, branching and merging untouched history are all unaffected. An admin
671 may override, audited, for the same reason a force-push is allowed: otherwise a lock whose
672 holder has left makes a path unpushable forever.
673
674 `/r/<repo>/locks` answers "who is holding what", which the protocol otherwise hides
675 completely — the next person to push is refused with no way to find out why without asking
676 somebody.
677 (Measured: `TestALockRefusesAPushUntilItIsReleased` drives it through the binary over SSH —
678 lock, refused push naming the holder, unlock, the same push landing; `TestLocksAre-
679 AdvertisedOnlyWhenOffered` pins that the capability follows the lock table, since a client
680 that reads `locking` will offer a command that must work; the store's own tests cover path
681 normalization (one file, one lock, however it was typed), paging, owner partitioning and
682 the audited override.)
683
684 **Known gap, stated rather than assumed:** git-lfs is not installed in this environment, so
685 the protocol was implemented against `docs/proposals/ssh_adapter.md` and verified with a
686 client written to that document, not with a shipped client. The framing, the 201/409
687 argument blocks and the five-line lock-spec records are the spec's; whether a real
688 `git lfs lock` agrees is the first thing to check on a machine that has one.
651 689
652 - (Artifact uploads from workflows landed in Phase 7 as run artifacts: a job declares
653 `artifacts:`, the runner publishes the matches to the object store, and the run page
654 serves them. See Phase 7 slice 6.)
655 690 ✅ **Slice 6 — objects are scoped to the repository that owns them.** The store was
656 691 instance-wide and content-addressed, so an object was fetchable through *any* repository
657 692 whose read rule you passed, provided you knew its sha256. That is now impossible, and the
@@ -692,46 +727,6 @@ slow — and `gc` is an operator command, not a hot path. The link pass makes th
692 727 stronger: the thing that repairs the store is the same walk that cleans it, and it cannot
693 728 invent a reference that does not exist.
694 729
695 ✅ **Slice 5 — file locking.** `git lfs lock <path>` works over the SSH transfer, so a
696 binary that git cannot merge can be reserved by one person before they edit it: a push
697 that changes a locked path is refused, naming the holder, and the lock is released when
698 its holder says so. This is what makes a repository of design files usable by more than
699 one person, and it is the reason a studio would pick a forge with it over one without.
700
701 The HTTP locking API is the usual path for git-lfs, and it is a *write* surface — which
702 this instance does not have, because its web is anonymous and read-only. So locking rides
703 the SSH transfer protocol instead (`lock`, `list-lock`, `unlock`, advertised as the
704 `locking` capability), which means the key that pushed is the key that locks: no second
705 endpoint, no token, nothing new to authenticate. Locks live in `data/users.db` beside the
706 roster and the access rules — the file that holds what git cannot rebuild — because a lock
707 resurrected from history would be exactly wrong: the point of a lock is that it is
708 released. They are keyed by account id, so a rename changes nothing.
709
710 The interesting decision is *where the refusal lives*. git-lfs enforces locks on the
711 client: it asks the server what is held and declines to send the push. That is a courtesy,
712 and a plain `git push` walks straight past it — so the rule also sits in `pre-receive`, at
713 the one place this forge enforces everything else, where it cannot be turned off by a
714 client setting. What it guards is narrow on purpose: only the paths the push actually
715 writes, so reading, branching and merging untouched history are all unaffected. An admin
716 may override, audited, for the same reason a force-push is allowed: otherwise a lock whose
717 holder has left makes a path unpushable forever.
718
719 `/r/<repo>/locks` answers "who is holding what", which the protocol otherwise hides
720 completely — the next person to push is refused with no way to find out why without asking
721 somebody.
722 (Measured: `TestALockRefusesAPushUntilItIsReleased` drives it through the binary over SSH —
723 lock, refused push naming the holder, unlock, the same push landing; `TestLocksAre-
724 AdvertisedOnlyWhenOffered` pins that the capability follows the lock table, since a client
725 that reads `locking` will offer a command that must work; the store's own tests cover path
726 normalization (one file, one lock, however it was typed), paging, owner partitioning and
727 the audited override.)
728
729 **Known gap, stated rather than assumed:** git-lfs is not installed in this environment, so
730 the protocol was implemented against `docs/proposals/ssh_adapter.md` and verified with a
731 client written to that document, not with a shipped client. The framing, the 201/409
732 argument blocks and the five-line lock-spec records are the spec's; whether a real
733 `git lfs lock` agrees is the first thing to check on a machine that has one.
734
735 730 **Exit criteria:** a clone of an LFS repository pulls real objects through the proxy
736 731 (measured: a 256 KiB artifact pushed over SSH and pulled back byte-identical via
737 732 `http://…/git/<repo>.git`, with the sha256 checked on both ends); a pointer pushed