241 lines
7 KiB
Nix
241 lines
7 KiB
Nix
{ config, lib, ... }:
|
|
|
|
let
|
|
inherit (lib) mkOption types;
|
|
cfg = config.dev-env;
|
|
|
|
# Parse a forgejo URL into { namespace, repo, ref }.
|
|
#
|
|
# Accepts:
|
|
# git+ssh://forgejo@host/org/repo.git?ref=branch
|
|
# git+ssh://forgejo@host/org/repo?ref=branch
|
|
# git+https://host/org/repo.git?ref=branch
|
|
# git+https://host/org/repo?ref=branch
|
|
#
|
|
# Returns null if the URL does not match a forgejo host.
|
|
#
|
|
# Implementation note: Nix's `builtins.match` uses POSIX ERE, which
|
|
# does NOT support non-greedy quantifiers (`+?`/`*?`). A naive regex
|
|
# like `([^?]+?)(\.git)?` lets the `+?` act greedy, producing a repo
|
|
# name of `webapp.git`. Instead we match everything before `?ref=`
|
|
# or end-of-string as the raw repo name, then strip `.git` after.
|
|
parseForgejoUrl =
|
|
url:
|
|
let
|
|
host = cfg.forgejo.host;
|
|
# One regex covering both schemes. Groups:
|
|
# 1: namespace
|
|
# 2: repo (possibly with .git suffix)
|
|
# 3: optional "?ref=..." (kept for completeness)
|
|
# 4: optional ref value
|
|
sshMatch = builtins.match "git\\+ssh://[^@]+@${host}/([^/]+)/([^?]+)(\\?ref=(.+))?" url;
|
|
httpsMatch = builtins.match "git\\+https://${host}/([^/]+)/([^?]+)(\\?ref=(.+))?" url;
|
|
m =
|
|
if sshMatch != null then
|
|
sshMatch
|
|
else if httpsMatch != null then
|
|
httpsMatch
|
|
else
|
|
null;
|
|
in
|
|
if m == null then
|
|
null
|
|
else
|
|
{
|
|
namespace = builtins.elemAt m 0;
|
|
repo = lib.removeSuffix ".git" (builtins.elemAt m 1);
|
|
ref = builtins.elemAt m 3;
|
|
};
|
|
|
|
# mkProject: shorthand constructor for `dev-env.projects.<name>`.
|
|
#
|
|
# Fills in conventional defaults so users write less boilerplate.
|
|
# The returned attrset matches the `projectType` submodule schema.
|
|
mkProject =
|
|
{
|
|
name ? null,
|
|
category ? null,
|
|
upstream ? null,
|
|
forgejoRepo ? null, # defaults to "${cfg.forgejo.org}/${name}"
|
|
originUrl ? null, # override origin URL (e.g. GitHub-only repos)
|
|
githubFork ? null, # defaults from github.forkUser
|
|
worktrees ? { },
|
|
isClone ? false,
|
|
deployFlakeInput ? null,
|
|
worktreeRoot ? null, # defaults to name
|
|
}:
|
|
{
|
|
forgejoRepo =
|
|
if forgejoRepo != null then
|
|
forgejoRepo
|
|
else if originUrl != null then
|
|
null
|
|
else if name != null then
|
|
"${cfg.forgejo.org}/${name}"
|
|
else
|
|
throw "dev-env.lib.mkProject: set forgejoRepo, originUrl, or name";
|
|
inherit
|
|
originUrl
|
|
upstream
|
|
category
|
|
isClone
|
|
deployFlakeInput
|
|
;
|
|
githubFork =
|
|
if githubFork != null then
|
|
githubFork
|
|
else if upstream != null && cfg.github.forkUser != null && name != null then
|
|
"git@github.com:${cfg.github.forkUser}/${name}.git"
|
|
else
|
|
null;
|
|
worktreeRoot =
|
|
if worktreeRoot != null then
|
|
worktreeRoot
|
|
else if name != null then
|
|
name
|
|
else
|
|
"";
|
|
inherit worktrees;
|
|
};
|
|
|
|
# deriveFromFlakeInput: parse one flake input url into a project entry
|
|
# (or null if the URL doesn't belong to our forgejo).
|
|
#
|
|
# Input: { name = "webapp-host5"; url = "git+ssh://forgejo@git.atitlan.io/aiolabs/webapp.git?ref=host5"; }
|
|
# Output: { namespace = "aiolabs"; repo = "webapp"; ref = "host5"; }
|
|
deriveFromFlakeInput =
|
|
{ inputName, url }:
|
|
let
|
|
parsed = parseForgejoUrl url;
|
|
in
|
|
if parsed == null then
|
|
null
|
|
else
|
|
{
|
|
inputName = inputName;
|
|
repo = parsed.repo;
|
|
namespace = parsed.namespace;
|
|
ref = parsed.ref;
|
|
};
|
|
|
|
# Collapse a list of `{ inputName, namespace, repo, ref }` into
|
|
# `{ <repo> = { forgejoRepo, worktrees = { <ref> = { branch = ref; }; ... }; }; }`.
|
|
#
|
|
# Multiple inputs can share the same repo with different refs; this is
|
|
# the common case for webapp + webapp-dev — both become worktrees of
|
|
# `webapp` (main and dev respectively).
|
|
groupDerivedProjects =
|
|
parsedList:
|
|
lib.foldl' (
|
|
acc: entry:
|
|
if entry == null || entry.ref == null then
|
|
acc
|
|
else
|
|
let
|
|
key = entry.repo;
|
|
existing =
|
|
acc.${key} or {
|
|
forgejoRepo = "${entry.namespace}/${entry.repo}";
|
|
originUrl = null;
|
|
worktreeRoot = entry.repo;
|
|
worktrees = { };
|
|
upstream = null;
|
|
githubFork = null;
|
|
category = null;
|
|
isClone = false;
|
|
deployFlakeInput = null;
|
|
};
|
|
newWorktree = {
|
|
branch = entry.ref;
|
|
path = null;
|
|
remote = "origin";
|
|
};
|
|
in
|
|
acc
|
|
// {
|
|
${key} = existing // {
|
|
worktrees = existing.worktrees // {
|
|
${entry.ref} = newWorktree;
|
|
};
|
|
# Record the LAST input name that contributed — good enough
|
|
# for --override-input wiring.
|
|
deployFlakeInput = entry.inputName;
|
|
};
|
|
}
|
|
) { } parsedList;
|
|
|
|
# Resolve worktree filesystem paths. Projects with category live under
|
|
# ${root}/${category}/${worktreeRoot}/<worktree>; otherwise
|
|
# ${root}/${worktreeRoot}/<worktree>.
|
|
worktreePath =
|
|
projectName: project: worktreeName:
|
|
let
|
|
root = cfg.root;
|
|
sub =
|
|
if project.category != null then
|
|
"${project.category}/${project.worktreeRoot}"
|
|
else
|
|
project.worktreeRoot;
|
|
leaf =
|
|
let
|
|
wt = project.worktrees.${worktreeName} or null;
|
|
in
|
|
if wt != null && wt.path != null then wt.path else worktreeName;
|
|
in
|
|
"${root}/${sub}/${leaf}";
|
|
|
|
# Bare repo path (always ${root}/repos/<basename>.git).
|
|
# Falls back to projectName when forgejoRepo is null (GitHub-only projects).
|
|
bareRepoPath =
|
|
projectName: project:
|
|
let
|
|
basename =
|
|
if project.forgejoRepo != null then
|
|
lib.last (lib.splitString "/" project.forgejoRepo)
|
|
else
|
|
projectName;
|
|
in
|
|
"${cfg.root}/repos/${basename}.git";
|
|
|
|
# Construct the remotes for a project. `origin` is `originUrl` if set,
|
|
# otherwise derived from the forgejo host + forgejoRepo. Projects with
|
|
# neither end up with no origin (filtered out below).
|
|
projectRemotes =
|
|
project:
|
|
lib.filterAttrs (_: v: v != null) {
|
|
origin =
|
|
if project.originUrl or null != null then
|
|
project.originUrl
|
|
else if project.forgejoRepo != null then
|
|
"${cfg.forgejo.sshUser}@${cfg.forgejo.host}:${project.forgejoRepo}.git"
|
|
else
|
|
null;
|
|
upstream = project.upstream;
|
|
github-fork = project.githubFork;
|
|
};
|
|
|
|
helpers = {
|
|
inherit
|
|
mkProject
|
|
deriveFromFlakeInput
|
|
parseForgejoUrl
|
|
groupDerivedProjects
|
|
worktreePath
|
|
bareRepoPath
|
|
projectRemotes
|
|
;
|
|
};
|
|
|
|
in
|
|
|
|
{
|
|
options.dev-env.lib = mkOption {
|
|
type = types.attrs;
|
|
internal = true;
|
|
description = "dev-env module helper functions (internal).";
|
|
};
|
|
|
|
config = {
|
|
dev-env.lib = helpers;
|
|
};
|
|
}
|