@@ -5,6 +5,7 @@ S11 ships the create-a-repo flow end-to-end: a logged-in user clicks **New**, fi |
| 5 | 5 | ## What's wired |
| 6 | 6 | |
| 7 | 7 | - **Migration:** `0017_repos.sql` adds the `repos` table (with `repo_visibility` enum, owner XOR check, per-owner unique-by-name partial indexes, soft-delete column). |
| 8 | +- **Source remotes:** `0051_repo_source_remotes.sql` adds one optional public fetch URL per repo. Creation and settings can save this URL, fetch heads/tags, and use it later for submodule gitlink backfill. |
| 8 | 9 | - **sqlc package:** `internal/repos/sqlc` (`reposdb`) — Create, Get-by-owner-and-name, Exists, List-by-owner, Count, SoftDelete, UpdateDiskUsed. |
| 9 | 10 | - `internal/repos/validate.go` — name shape (≤100 chars, `[a-z0-9._-]`, non-separator edges, no dot-dot, no leading dot) + reserved-name list. |
| 10 | 11 | - `internal/repos/templates/` — embeds 10 SPDX licenses + 10 .gitignore templates + a minimal README generator. Sourced from gitea's `options/license` and `options/gitignore` (originally github.com/github/gitignore, MIT/CC0). |
@@ -43,6 +44,9 @@ POST /new |
| 43 | 44 | ├─ ValidateName / ValidateDescription (friendly error if bad shape) |
| 44 | 45 | ├─ Visibility ∈ {"public", "private"} |
| 45 | 46 | ├─ License/Gitignore keys ∈ curated list (when set) |
| 47 | + ├─ Optional source_remote_url: |
| 48 | + │ normalize + SSRF-validate a public http(s) Git remote |
| 49 | + │ refuse credentials/query/fragment and any init-template combo |
| 46 | 50 | ├─ Limiter.Hit(scope=repo_create, ident=user:<id>, max=10/hour) |
| 47 | 51 | ├─ Resolve author = display name + verified primary email |
| 48 | 52 | │ (refuse with ErrNoVerifiedEmail when init is requested AND missing) |
@@ -55,6 +59,11 @@ POST /new |
| 55 | 59 | │ (hash-object → update-index → write-tree → commit-tree → update-ref) |
| 56 | 60 | ├─ tx.Commit() |
| 57 | 61 | ├─ audit.Record(action=repo_created, target=repo, target_id=<repo.id>) |
| 62 | + ├─ if source_remote_url set: |
| 63 | + │ repo_source_remotes UPSERT |
| 64 | + │ git fetch --no-recurse-submodules heads/tags from that remote |
| 65 | + │ update default_branch/default_branch_oid from fetched refs |
| 66 | + │ enqueue index + size recalculation |
| 58 | 67 | └─ return Result{Repo, InitialCommitOID, DiskPath} |
| 59 | 68 | ``` |
| 60 | 69 | |
@@ -65,6 +74,34 @@ Failure handling at each step: |
| 65 | 74 | - Initial-commit error: same as above — Rollback + RemoveAll. |
| 66 | 75 | - tx.Commit error: post-FS-success but DB couldn't commit. We RemoveAll the bare repo dir to keep DB and disk consistent. |
| 67 | 76 | - Audit error: logged at WARN, not propagated — we don't fail the create just because audit logging blipped. |
| 77 | +- Source remote fetch error: the repo remains created, the URL is retained with `last_error`, and the user lands on General settings where they can fix or retry the remote. |
| 78 | + |
| 79 | +## Source remotes and imports |
| 80 | + |
| 81 | +Source remotes are for public Git import/mirror metadata, not private |
| 82 | +credentials. The accepted shape is `http://` or `https://`, a host, and |
| 83 | +a non-empty repository path; userinfo, query strings, and fragments are |
| 84 | +rejected so secrets do not enter the database or logs. Before storing or |
| 85 | +fetching, the URL runs through `internal/security/ssrf` with DNS |
| 86 | +resolution so loopback/private/CGNAT/link-local hosts are rejected. |
| 87 | + |
| 88 | +Fetches use `internal/repos/git.FetchRemoteHeadsAndTags`, which shells |
| 89 | +out to canonical git with `--no-recurse-submodules` and non-forcing |
| 90 | +head/tag refspecs. If the local branch diverged, git rejects the update; |
| 91 | +shithub records the fetch error instead of overwriting local history. |
| 92 | +After a successful fetch, shithub keeps the current default branch if it |
| 93 | +exists, otherwise prefers `trunk`, then `main`, then `master`, then the |
| 94 | +first fetched branch. The chosen branch OID becomes |
| 95 | +`repos.default_branch_oid`, making the Code tab and history views work |
| 96 | +without a later push. |
| 97 | + |
| 98 | +The same stored remote is used by submodule rendering. If a parent repo |
| 99 | +pins a submodule commit that the local target repo lacks, shithub tries |
| 100 | +the target repo's source remote before any GitHub-name fallback. This is |
| 101 | +the durable path for self-hosted or non-GitHub upstreams: create/import |
| 102 | +each submodule repo with its source remote, then create/import the parent |
| 103 | +repo, and the pinned submodule links can hydrate exact detached tree |
| 104 | +views on demand. |
| 68 | 105 | |
| 69 | 106 | ## Plumbing-only initial commit |
| 70 | 107 | |