Compare commits

...

41 Commits

Author SHA1 Message Date
jolheiser 31714bf9af
tui 2025-06-10 12:24:47 -05:00
jolheiser 86aa09929f
a few more unit tests 2025-06-04 16:13:46 -05:00
jolheiser b8ca3fc4b8
fix tags 2025-06-04 16:06:14 -05:00
jolheiser ebe2dc4603
add last commit to index 2025-06-04 15:09:24 -05:00
jolheiser ea40aa746e
permalink 2025-06-04 13:47:09 -05:00
jolheiser 68992f0807
add app script to flake for VM 2025-05-14 11:53:15 -05:00
jolheiser 9f3ca2ae1e
testing vm 2025-05-14 10:01:41 -05:00
jolheiser 070544ef18
fix non-flake nix 2025-05-12 22:17:32 -05:00
jolheiser 9d6aa43bd4
add line numbers and TOC to commit page, clean up markup formatter 2025-04-23 12:10:46 -05:00
jolheiser 27915049a8
µgit 2025-04-21 20:55:48 -05:00
jolheiser dc88be2777
regenerate styles 2025-04-17 14:38:17 -05:00
jolheiser 801198a1f7
make entire codeblock scroll if needed 2025-04-17 14:29:42 -05:00
jolheiser 753d1f3d20
fix arrow on diff page 2025-04-11 14:42:17 -05:00
jolheiser f7bcb11792
this one is for the chrome enjoyers 2025-04-11 12:08:15 -05:00
jolheiser d502993daf
harden service 2025-04-11 10:44:54 -05:00
jolheiser 9358d37e07
add forge meta 2025-04-09 20:18:56 -05:00
jolheiser 04f4bc04e9
fix zellij layout 2025-03-23 11:56:29 -05:00
jolheiser 19b2f55a1a
add air 2025-03-23 11:43:47 -05:00
jolheiser 3b4c4dcc06
use raw for script 2025-03-10 13:53:58 -05:00
jolheiser 20a8451427
add tests 2025-02-23 12:45:51 -06:00
jolheiser d4b287ed08
trap sigterm instead of kill 2025-02-23 12:38:16 -06:00
jolheiser a9208d2fe2
generate default meta 2025-02-23 12:29:36 -06:00
jolheiser 5809db19f5
multiple nix module instances 2025-02-13 22:36:27 -06:00
jolheiser 15c0850bab
change charm log to slog 2025-02-11 19:08:23 -06:00
jolheiser 53945f5a62
remove tailscale directly
Integrating with tailscale can now be done via tailproxy. 
This will require two ugit instances, but it allows me to remove the (bulky) dependency directly.
2025-02-11 10:02:24 -06:00
jolheiser d30862f6fd
remove ugit-uci 2025-02-02 22:08:54 -06:00
jolheiser b205fa2103
convert to gomponents 2025-02-02 22:08:54 -06:00
jolheiser 4b90e77eed
update go modules 2025-01-08 12:39:54 -06:00
jolheiser 0f4b6ec936
set max-width for images in markdown 2024-10-23 22:04:40 -05:00
jolheiser 3a4e09a218
feat: scroll highlighted line into view and add copy button
Signed-off-by: jolheiser <git@jolheiser.com>
2024-10-22 12:25:23 -05:00
jolheiser 03fa43600d
chore: update chroma for jsonnet lexer
Signed-off-by: jolheiser <git@jolheiser.com>
2024-10-16 15:32:34 -05:00
jolheiser 39efc9d853
chore: update templ
Signed-off-by: jolheiser <git@jolheiser.com>
2024-10-11 16:02:34 -05:00
jolheiser 9f70cc24cd
fix: sort dirs correctly
Signed-off-by: jolheiser <git@jolheiser.com>
2024-09-24 21:27:41 -05:00
jolheiser 4fbb4cf289
fix: index in search dedupe
Signed-off-by: jolheiser <git@jolheiser.com>
2024-08-18 22:02:15 -05:00
jolheiser 0ae76c9d74
feat: split out nix package/module
Signed-off-by: jolheiser <git@jolheiser.com>
2024-08-12 23:53:49 -05:00
jolheiser 3faedb8236
fix: search results
Signed-off-by: jolheiser <git@jolheiser.com>
2024-08-10 18:15:27 -05:00
jolheiser 11d534f89f
feat: allow regex search
Signed-off-by: jolheiser <git@jolheiser.com>
2024-08-10 12:16:19 -05:00
jolheiser 00b0d91307
fix: test hook expansion
Signed-off-by: jolheiser <git@jolheiser.com>
2024-08-01 14:20:15 -05:00
jolheiser 590777dea0
feat: custom lexer registry
Signed-off-by: jolheiser <git@jolheiser.com>
2024-07-31 22:18:17 -05:00
jolheiser df7ace7fae
feat: ugit-uci
Signed-off-by: jolheiser <git@jolheiser.com>
2024-07-31 22:09:52 -05:00
jolheiser 79c3e14082
feat: pre-receive.d
Signed-off-by: jolheiser <git@jolheiser.com>
2024-07-29 14:09:43 -05:00
80 changed files with 2776 additions and 3846 deletions

24
.air.toml 100644
View File

@ -0,0 +1,24 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
bin = "./ugitd"
cmd = "go build ./cmd/ugitd"
delay = 1000
exclude_file = ["internal/html/tailwind.go"]
exclude_regex = ["_test.go"]
exclude_unchanged = true
include_ext = ["go"]
pre_cmd = ["go generate ./..."]
[misc]
clean_on_exit = true
[proxy]
app_port = 8449
enabled = true
proxy_port = 8450
[screen]
clear_on_rebuild = true

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
.ssh/
.ugit/
.tsnet/
*.qcow2

View File

@ -1,5 +0,0 @@
[[language]]
name = "templ"
language-id = "html"
language-servers = ["templ", "vscode-html-language-server", "tailwindcss-ls"]

View File

@ -2,17 +2,17 @@
<picture>
<img alt="ugit logo" width="250" src="./assets/ugit.svg" />
</picture>
<h3 align="center">ugit</h3>
<h3 align="center">µgit</h3>
</p>
Minimal git server
ugit allows cloning via HTTPS/SSH, but can only be pushed to via SSH.
µgit allows cloning via HTTPS/SSH, but can only be pushed to via SSH.
There are no plans to directly support issues or PR workflows, although webhooks are planned and auxillary software may be created to facilitate these things.
If you wish to collaborate, please send me patches via [git-pr](https://pr.jolheiser.com/repos/ugit).
Currently all HTML is allowed in markdown, ugit is intended to be run by/for a trusted user.
Currently all HTML is allowed in markdown, µgit is intended to be run by/for a trusted user.
## Getting your public SSH keys from another forge

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 24 24" stroke="#de4c36" fill="#de4c36" stroke-width="1" xmlns="http://www.w3.org/2000/svg">
<title>ugit icon</title>
<title>µgit icon</title>
<rect fill="none" x="1" y="1" rx="1" ry="1" width="22" height="22"></rect>
<ellipse cx="6" cy="6" rx="2" ry="2"></ellipse>
<ellipse cx="18" cy="6" rx="2" ry="2"></ellipse>

Before

Width:  |  Height:  |  Size: 543 B

After

Width:  |  Height:  |  Size: 544 B

View File

@ -3,9 +3,9 @@ package main
import (
"flag"
"fmt"
"log/slog"
"strings"
"github.com/charmbracelet/log"
"github.com/peterbourgon/ff/v3"
"github.com/peterbourgon/ff/v3/ffyaml"
)
@ -17,7 +17,8 @@ type cliArgs struct {
Meta metaArgs
Profile profileArgs
Log logArgs
Tailscale tailscaleArgs
ShowPrivate bool
TUI bool
}
type sshArgs struct {
@ -51,16 +52,10 @@ type profileLink struct {
}
type logArgs struct {
Level log.Level
Level slog.Level
JSON bool
}
type tailscaleArgs struct {
Enable bool
Hostname string
DataDir string
}
func parseArgs(args []string) (c cliArgs, e error) {
fs := flag.NewFlagSet("ugitd", flag.ContinueOnError)
fs.String("config", "ugit.yaml", "Path to config file")
@ -84,25 +79,30 @@ func parseArgs(args []string) (c cliArgs, e error) {
Description: "Minimal git server",
},
Log: logArgs{
Level: log.InfoLevel,
},
Tailscale: tailscaleArgs{
Enable: false,
Hostname: "ugit",
DataDir: ".tsnet",
Level: slog.LevelError,
},
}
fs.Func("log.level", "Logging level", func(s string) error {
lvl, err := log.ParseLevel(s)
if err != nil {
return err
var lvl slog.Level
switch strings.ToLower(s) {
case "debug":
lvl = slog.LevelDebug
case "info":
lvl = slog.LevelInfo
case "warn", "warning":
lvl = slog.LevelWarn
case "error":
lvl = slog.LevelError
default:
return fmt.Errorf("unknown log level %q: options are [debug, info, warn, error]", s)
}
c.Log.Level = lvl
return nil
})
fs.BoolVar(&c.Log.JSON, "log.json", c.Log.JSON, "Print logs in JSON(L) format")
fs.StringVar(&c.RepoDir, "repo-dir", c.RepoDir, "Path to directory containing repositories")
fs.BoolVar(&c.ShowPrivate, "show-private", c.ShowPrivate, "Show private repos in web interface")
fs.BoolVar(&c.SSH.Enable, "ssh.enable", c.SSH.Enable, "Enable SSH server")
fs.StringVar(&c.SSH.AuthorizedKeys, "ssh.authorized-keys", c.SSH.AuthorizedKeys, "Path to authorized_keys")
fs.StringVar(&c.SSH.CloneURL, "ssh.clone-url", c.SSH.CloneURL, "SSH clone URL base")
@ -115,6 +115,7 @@ func parseArgs(args []string) (c cliArgs, e error) {
fs.StringVar(&c.Meta.Description, "meta.description", c.Meta.Description, "App description")
fs.StringVar(&c.Profile.Username, "profile.username", c.Profile.Username, "Username for index page")
fs.StringVar(&c.Profile.Email, "profile.email", c.Profile.Email, "Email for index page")
fs.BoolVar(&c.TUI, "tui", c.TUI, "Run the TUI interface directly")
fs.Func("profile.links", "Link(s) for index page", func(s string) error {
parts := strings.SplitN(s, ",", 2)
if len(parts) != 2 {
@ -126,9 +127,6 @@ func parseArgs(args []string) (c cliArgs, e error) {
})
return nil
})
fs.BoolVar(&c.Tailscale.Enable, "tailscale.enable", c.Tailscale.Enable, "Enable Tailscale")
fs.StringVar(&c.Tailscale.Hostname, "tailscale.hostname", c.Tailscale.Hostname, "Tailscale host to show private repos on")
fs.StringVar(&c.Tailscale.DataDir, "tailscale.data-dir", c.Tailscale.DataDir, "Tailscale data/state directory")
return c, ff.Parse(fs, args,
ff.WithEnvVarPrefix("UGIT"),

View File

@ -4,22 +4,23 @@ import (
"errors"
"flag"
"fmt"
"log"
"log/slog"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/charmbracelet/log"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/httplog/v2"
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
"github.com/go-git/go-git/v5/utils/trace"
"go.jolheiser.com/tailroute"
"go.jolheiser.com/ugit/internal/git"
"go.jolheiser.com/ugit/internal/http"
"go.jolheiser.com/ugit/internal/ssh"
"go.jolheiser.com/ugit/internal/tui"
)
func main() {
@ -40,14 +41,22 @@ func main() {
panic(err)
}
log.SetLevel(args.Log.Level)
// Run TUI mode if requested
if args.TUI {
if err := tui.Run(args.RepoDir); err != nil {
panic(err)
}
return
}
slog.SetLogLoggerLevel(args.Log.Level)
middleware.DefaultLogger = httplog.RequestLogger(httplog.NewLogger("ugit", httplog.Options{
JSON: args.Log.JSON,
LogLevel: slog.Level(args.Log.Level),
Concise: args.Log.Level != log.DebugLevel,
Concise: args.Log.Level != slog.LevelDebug,
}))
if args.Log.Level == log.DebugLevel {
if args.Log.Level == slog.LevelDebug {
trace.SetTarget(trace.Packet)
} else {
middleware.DefaultLogger = http.NoopLogger
@ -55,7 +64,8 @@ func main() {
}
if args.Log.JSON {
log.SetFormatter(log.JSONFormatter)
logger := slog.New(slog.NewJSONHandler(os.Stderr, nil))
slog.SetDefault(logger)
}
if err := requiredFS(args.RepoDir); err != nil {
@ -75,7 +85,7 @@ func main() {
panic(err)
}
go func() {
log.Debugf("SSH listening on ssh://localhost:%d\n", sshSettings.Port)
log.Printf("SSH listening on ssh://localhost:%d\n", sshSettings.Port)
if err := sshSrv.ListenAndServe(); err != nil {
panic(err)
}
@ -92,7 +102,7 @@ func main() {
Username: args.Profile.Username,
Email: args.Profile.Email,
},
ShowPrivate: false,
ShowPrivate: args.ShowPrivate,
}
for _, link := range args.Profile.Links {
httpSettings.Profile.Links = append(httpSettings.Profile.Links, http.Link{
@ -103,30 +113,15 @@ func main() {
if args.HTTP.Enable {
httpSrv := http.New(httpSettings)
go func() {
log.Debugf("HTTP listening on http://localhost:%d\n", httpSettings.Port)
log.Printf("HTTP listening on http://localhost:%d\n", httpSettings.Port)
if err := httpSrv.ListenAndServe(); err != nil {
panic(err)
}
}()
}
if args.Tailscale.Enable {
tailnetSettings := httpSettings
tailnetSettings.ShowPrivate = true
tailnetSrv := http.New(tailnetSettings)
tr := tailroute.Router{
Tailnet: tailnetSrv.Mux,
}
go func() {
log.Debugf("Tailnet listening on http://%s\n", args.Tailscale.Hostname)
if err := tr.Serve(args.Tailscale.Hostname, args.Tailscale.DataDir); err != nil {
panic(err)
}
}()
}
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Kill, os.Interrupt)
signal.Notify(ch, syscall.SIGTERM, os.Interrupt)
<-ch
}
@ -149,12 +144,20 @@ func requiredFS(repoDir string) error {
}
fp = filepath.Join(fp, "pre-receive")
if err := os.MkdirAll(fp+".d", os.ModePerm); err != nil {
return err
}
fi, err := os.Create(fp)
if err != nil {
return err
}
fi.WriteString("#!/usr/bin/env bash\n")
fi.WriteString(fmt.Sprintf("%s pre-receive-hook\n", bin))
fi.WriteString(fmt.Sprintf(`for hook in %s.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
"${hook}"
done`, fp))
fi.Close()
return os.Chmod(fp, 0o755)

View File

@ -5,19 +5,13 @@ layout {
command "nix"
args "develop"
size "90%"
start_suspended true
}
pane split_direction="vertical" {
pane {
name "run"
command "go"
args "run" "./cmd/ugitd"
start_suspended true
}
pane {
name "watch"
name "air"
command "nix"
args "develop" "--command" "nu" "-c" "watch --glob *.templ ./internal/html/ {|| go generate ./...}"
args "develop" "--command" "air"
start_suspended true
}
}
}

View File

@ -1,51 +1,12 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gomod2nix": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1717050755,
"narHash": "sha256-C9IEHABulv2zEDFA+Bf0E1nmfN4y6MIUe5eM2RCrDC0=",
"owner": "nix-community",
"repo": "gomod2nix",
"rev": "31b6d2e40b36456e792cd6cf50d5a8ddd2fa59a1",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "gomod2nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1719379843,
"narHash": "sha256-u+D+IOAMMl70+CJ9NKB+RMrASjInuIWMHzjLWQjPZ6c=",
"lastModified": 1736241350,
"narHash": "sha256-CHd7yhaDigUuJyDeX0SADbTM9FXfiWaeNyY34FL1wQU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "b3f3c1b13fb08f3828442ee86630362e81136bbc",
"rev": "8c9fd3e564728e90829ee7dbac6edc972971cd0f",
"type": "github"
},
"original": {
@ -57,27 +18,11 @@
},
"root": {
"inputs": {
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs",
"tailwind-ctp": "tailwind-ctp",
"tailwind-ctp-lsp": "tailwind-ctp-lsp"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"tailwind-ctp": {
"inputs": {
"nixpkgs": [

212
flake.nix
View File

@ -3,10 +3,6 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
gomod2nix = {
url = "github:nix-community/gomod2nix";
inputs.nixpkgs.follows = "nixpkgs";
};
tailwind-ctp = {
url = "git+https://git.jolheiser.com/tailwind-ctp";
inputs.nixpkgs.follows = "nixpkgs";
@ -17,172 +13,76 @@
};
};
outputs = {
outputs =
{
self,
nixpkgs,
gomod2nix,
tailwind-ctp,
tailwind-ctp-lsp,
} @ inputs: let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
tailwind-ctp = inputs.tailwind-ctp.packages.${system}.default;
tailwind-ctp-lsp = inputs.tailwind-ctp-lsp.packages.${system}.default;
ugit = gomod2nix.legacyPackages.${system}.buildGoApplication rec {
name = "ugitd";
src = pkgs.nix-gitignore.gitignoreSource [] (builtins.path {
inherit name;
path = ./.;
});
pwd = ./.;
subPackages = ["cmd/ugitd"];
CGO_ENABLED = 0;
flags = [
"-trimpath"
}:
let
systems = [
"x86_64-linux"
"i686-linux"
"x86_64-darwin"
"aarch64-linux"
"armv6l-linux"
"armv7l-linux"
];
ldflags = [
"-s"
"-w"
"-extldflags -static"
];
meta = with pkgs.lib; {
description = "Minimal git server";
homepage = "https://git.jolheiser.com/ugit";
maintainers = with maintainers; [jolheiser];
mainProgram = "ugitd";
};
};
in {
packages.${system}.default = ugit;
devShells.${system}.default = pkgs.mkShell {
forAllSystems = f: nixpkgs.lib.genAttrs systems f;
tctp = forAllSystems (system: tailwind-ctp.packages.${system}.default);
tctpl = forAllSystems (system: tailwind-ctp-lsp.packages.${system}.default);
in
{
packages = forAllSystems (system: import ./nix { pkgs = import nixpkgs { inherit system; }; });
devShells = forAllSystems (
system:
let
pkgs = import nixpkgs { inherit system; };
in
{
default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
go
gopls
gomod2nix.legacyPackages.${system}.gomod2nix
templ
tailwind-ctp
tailwind-ctp-lsp
air
tctp.${system}
tctpl.${system}
vscode-langservers-extracted
];
};
nixosModules.default = {
pkgs,
lib,
config,
...
}: let
cfg = config.services.ugit;
yamlFormat = pkgs.formats.yaml {};
configFile = pkgs.writeText "ugit.yaml" (builtins.readFile (yamlFormat.generate "ugit-yaml" cfg.config));
authorizedKeysFile = pkgs.writeText "ugit_keys" (builtins.concatStringsSep "\n" cfg.authorizedKeys);
in {
options = let
inherit (lib) mkEnableOption mkOption types;
in {
services.ugit = {
enable = mkEnableOption "Enable ugit";
package = mkOption {
type = types.package;
description = "ugit package to use";
default = ugit;
}
);
nixosModules.default = import ./nix/module.nix;
nixosConfigurations.ugitVM = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./nix/vm.nix
{
virtualisation.vmVariant.virtualisation = {
cores = 2;
memorySize = 2048;
graphics = false;
};
tsAuthKey = mkOption {
type = types.str;
description = "Tailscale one-time auth-key";
default = "";
};
repoDir = mkOption {
type = types.str;
description = "where ugit stores repositories";
default = "/var/lib/ugit/repos";
};
authorizedKeys = mkOption {
type = types.listOf types.str;
description = "list of keys to use for authorized_keys";
default = [];
};
authorizedKeysFile = mkOption {
type = types.str;
description = "path to authorized_keys file ugit uses for auth";
default = "/var/lib/ugit/authorized_keys";
};
hostKeyFile = mkOption {
type = types.str;
description = "path to host key file (will be created if it doesn't exist)";
default = "/var/lib/ugit/ugit_ed25519";
};
config = mkOption {
type = types.attrs;
default = {};
description = "config.yaml contents";
};
user = mkOption {
type = types.str;
default = "ugit";
description = "User account under which ugit runs";
};
group = mkOption {
type = types.str;
default = "ugit";
description = "Group account under which ugit runs";
};
openFirewall = mkOption {
type = types.bool;
default = false;
};
};
};
config = lib.mkIf cfg.enable {
users.users."${cfg.user}" = {
home = "/var/lib/ugit";
createHome = true;
group = "${cfg.group}";
isSystemUser = true;
isNormalUser = false;
description = "user for ugit service";
};
users.groups."${cfg.group}" = {};
networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [8448 8449];
};
systemd.services.ugit = {
enable = true;
script = let
authorizedKeysPath =
if (builtins.length cfg.authorizedKeys) > 0
then authorizedKeysFile
else cfg.authorizedKeysFile;
args = [
"--config=${configFile}"
"--repo-dir=${cfg.repoDir}"
"--ssh.authorized-keys=${authorizedKeysPath}"
"--ssh.host-key=${cfg.hostKeyFile}"
system.stateVersion = "23.11";
}
];
in "${cfg.package}/bin/ugitd ${builtins.concatStringsSep " " args}";
wantedBy = ["multi-user.target"];
after = ["network.target"];
path = [cfg.package pkgs.git pkgs.bash];
serviceConfig = {
User = cfg.user;
Group = cfg.group;
Restart = "always";
RestartSec = "15";
WorkingDirectory = "/var/lib/ugit";
Environment = ["TS_AUTHKEY=${cfg.tsAuthKey}"];
};
};
};
apps = forAllSystems (
system:
let
pkgs = import nixpkgs { inherit system; };
in
{
vm = {
type = "app";
program = "${pkgs.writeShellScript "vm" ''
nixos-rebuild build-vm --flake .#ugitVM
./result/bin/run-nixos-vm
rm nixos.qcow2
''}";
};
}
);
};
}

171
go.mod
View File

@ -1,141 +1,84 @@
module go.jolheiser.com/ugit
go 1.22.3
go 1.23.1
toolchain go1.23.3
require (
github.com/a-h/templ v0.2.543
github.com/alecthomas/chroma/v2 v2.12.0
github.com/charmbracelet/log v0.3.1
github.com/charmbracelet/ssh v0.0.0-20240201134204-3f297de25560
github.com/charmbracelet/wish v1.3.0
github.com/alecthomas/assert/v2 v2.11.0
github.com/alecthomas/chroma/v2 v2.15.0
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.5
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/ssh v0.0.0-20241211182756-4fe22b0f1b7c
github.com/charmbracelet/wish v1.4.4
github.com/dustin/go-humanize v1.0.1
github.com/go-chi/chi/v5 v5.0.11
github.com/go-chi/chi/v5 v5.2.0
github.com/go-chi/httplog/v2 v2.1.1
github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.11.0
github.com/go-git/go-billy/v5 v5.6.1
github.com/go-git/go-git/v5 v5.13.1
github.com/peterbourgon/ff/v3 v3.4.0
github.com/yuin/goldmark v1.6.0
github.com/yuin/goldmark-emoji v1.0.2
github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-emoji v1.0.4
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
go.jolheiser.com/tailroute v0.0.0-20240726150858-67ef456b46b5
golang.org/x/net v0.23.0
golang.org/x/net v0.34.0
maragu.dev/gomponents v1.0.0
)
require (
dario.cat/mergo v1.0.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.4 // indirect
github.com/alecthomas/repr v0.4.0 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
github.com/aws/aws-sdk-go-v2/config v1.26.5 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/charmbracelet/bubbletea v0.25.0 // indirect
github.com/charmbracelet/keygen v0.5.0 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/charmbracelet/x/errors v0.0.0-20240130180102-bafe6fbaee60 // indirect
github.com/charmbracelet/x/exp/term v0.0.0-20240130180102-bafe6fbaee60 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 // indirect
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
github.com/creack/pty v1.1.21 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/keygen v0.5.1 // indirect
github.com/charmbracelet/log v0.4.0 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/conpty v0.1.0 // indirect
github.com/charmbracelet/x/errors v0.0.0-20250107110353-48b574af22a5 // indirect
github.com/charmbracelet/x/input v0.2.0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/charmbracelet/x/termios v0.1.0 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/creack/pty v1.1.24 // indirect
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
github.com/gaissmai/bart v0.4.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/csrf v1.7.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/illarion/gonotify v1.0.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/sdnotify v1.0.0 // indirect
github.com/mdlayher/socket v0.5.0 // indirect
github.com/miekg/dns v1.1.58 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mmcloughlin/avo v0.6.0 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/prometheus-community/pro-bing v0.4.0 // indirect
github.com/rivo/uniseg v0.4.6 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect
github.com/tailscale/wireguard-go v0.0.0-20240429185444-03c5a0ccf754 // indirect
github.com/tcnksm/go-httpstat v0.2.0 // indirect
github.com/u-root/u-root v0.12.0 // indirect
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/pjbgf/sha1cd v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.19.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.29.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 // indirect
nhooyr.io/websocket v1.8.10 // indirect
tailscale.com v1.68.2 // indirect
)

1
go.mod.sri 100644
View File

@ -0,0 +1 @@
sha256-L87PnM43gHrDcsRr3wnkB4e1Th2S0LsSwkXuebAFH44=

458
go.sum
View File

@ -1,195 +1,108 @@
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/a-h/templ v0.2.543 h1:8YyLvyUtf0/IE2nIwZ62Z/m2o2NqwhnMynzOL78Lzbk=
github.com/a-h/templ v0.2.543/go.mod h1:jP908DQCwI08IrnTalhzSEH9WJqG/Q94+EODQcJGFUA=
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.4 h1:G5U5asvD5N/6/36oIw3k2bOfBn5XVcZrb7PBjzzKKoE=
github.com/ProtonMail/go-crypto v1.1.4/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw=
github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU=
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE=
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/keygen v0.5.0 h1:XY0fsoYiCSM9axkrU+2ziE6u6YjJulo/b9Dghnw6MZc=
github.com/charmbracelet/keygen v0.5.0/go.mod h1:DfvCgLHxZ9rJxdK0DGw3C/LkV4SgdGbnliHcObV3L+8=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw=
github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g=
github.com/charmbracelet/ssh v0.0.0-20240201134204-3f297de25560 h1:kUYiDRF04AsyJvWi4HrklXXSU7+S7qjExusfDnYbVGg=
github.com/charmbracelet/ssh v0.0.0-20240201134204-3f297de25560/go.mod h1:IHy7o73i1MrQ5lmyJjjJ0g7y4+V+g69cm+Y7JCiZWPo=
github.com/charmbracelet/wish v1.3.0 h1:SYV5TIlzDb6WaxjkkYXxv2WZsTu/QZGwfGVc0UB5M48=
github.com/charmbracelet/wish v1.3.0/go.mod h1:1U/bI7zX+IE26ThD5gxtLgeRzctVhSrTpjucPqw4Pos=
github.com/charmbracelet/x/errors v0.0.0-20240130180102-bafe6fbaee60 h1:u0XFhTN81zvoGwyQWOYjjkrimEQj5L2DPECCX5cBsRw=
github.com/charmbracelet/x/errors v0.0.0-20240130180102-bafe6fbaee60/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/exp/term v0.0.0-20240130180102-bafe6fbaee60 h1:IV19YKUZVf6ATrhiPSCirZ4Bs7EsenYwOWcUHngV+q0=
github.com/charmbracelet/x/exp/term v0.0.0-20240130180102-bafe6fbaee60/go.mod h1:kOOxxyxgAFQVcR5yQJWTuLjzt5dR2pcgwy3WaLEudjE=
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 h1:Ig+OPkE3XQrrl+SKsOqAjlkrBN/zrr+Qpw7rCuDjRCE=
github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc=
github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/keygen v0.5.1 h1:zBkkYPtmKDVTw+cwUyY6ZwGDhRxXkEp0Oxs9sqMLqxI=
github.com/charmbracelet/keygen v0.5.1/go.mod h1:zznJVmK/GWB6dAtjluqn2qsttiCBhA5MZSiwb80fcHw=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/charmbracelet/ssh v0.0.0-20241211182756-4fe22b0f1b7c h1:treQxMBdI2PaD4eOYfFux8stfCkUxhuUxaqGcxKqVpI=
github.com/charmbracelet/ssh v0.0.0-20241211182756-4fe22b0f1b7c/go.mod h1:CY1xbl2z+ZeBmNWItKZyxx0zgDgnhmR57+DTsHOobJ4=
github.com/charmbracelet/wish v1.4.4 h1:wtfoAMkf8Db9zi+9Lme2f7XKMxL6BqfgDWbqcTUHLaU=
github.com/charmbracelet/wish v1.4.4/go.mod h1:XB8v51UxIFMRlUod9lLaAgOsj/wpe+qW9HjsoYIiNMo=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
github.com/charmbracelet/x/errors v0.0.0-20250107110353-48b574af22a5 h1:Hx72S6S4jAfrrWE3pv9IbudVdUV4htBgkOX800o17Bk=
github.com/charmbracelet/x/errors v0.0.0-20250107110353-48b574af22a5/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/input v0.2.0 h1:1Sv+y/flcqUfUH2PXNIDKDIdT2G8smOnGOgawqhwy8A=
github.com/charmbracelet/x/input v0.2.0/go.mod h1:KUSFIS6uQymtnr5lHVSOK9j8RvwTD4YHnWnzJUYnd/M=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/charmbracelet/x/termios v0.1.0 h1:y4rjAHeFksBAfGbkRDmVinMg7x7DELIGAFbdNvxg97k=
github.com/charmbracelet/x/termios v0.1.0/go.mod h1:H/EVv/KRnrYjz+fCYa9bsKdqF3S8ouDK0AZEbG7r+/U=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI=
github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40=
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ=
github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/gaissmai/bart v0.4.1 h1:G1t58voWkNmT47lBDawH5QhtTDsdqRIO+ftq5x4P9Ls=
github.com/gaissmai/bart v0.4.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/httplog/v2 v2.1.1 h1:ojojiu4PIaoeJ/qAO4GWUxJqvYUTobeo7zmuHQJAxRk=
github.com/go-chi/httplog/v2 v2.1.1/go.mod h1:/XXdxicJsp4BA5fapgIC3VuTD+z0Z/VzukoB3VDc1YE=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA=
github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg=
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA=
github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M=
github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f h1:ov45/OzrJG8EKbGjn7jJZQJTN7Z1t73sFYNIRd64YlI=
github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f/go.mod h1:JoDrYMZpDPYo6uH9/f6Peqms3zNNWT2XiGgioMOIGuI=
github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio=
github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@ -197,212 +110,96 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pjbgf/sha1cd v0.3.1 h1:Dh2GYdpJnO84lIw0LJwTFXjcNbasP/bklicSznyAaPI=
github.com/pjbgf/sha1cd v0.3.1/go.mod h1:Y8t7jSB/dEI/lQE04A1HVKteqjj9bX5O4+Cex0TCu8s=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw=
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk=
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w=
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU=
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g=
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
github.com/tailscale/wireguard-go v0.0.0-20240429185444-03c5a0ccf754 h1:iazWjqVHE6CbNam7WXRhi33Qad5o7a8LVYgVoILpZdI=
github.com/tailscale/wireguard-go v0.0.0-20240429185444-03c5a0ccf754/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9 h1:81P7rjnikHKTJ75EkjppvbwUfKHDHYk6LJpO5PZy8pA=
github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa h1:unMPGGK/CRzfg923allsikmvk2l7beBeFPUNC4RVX/8=
github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa/go.mod h1:Zj4Tt22fJVn/nz/y6Ergm1SahR9dio1Zm/D2/S0TmXM=
github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs=
github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s=
github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-emoji v1.0.4 h1:vCwMkPZSNefSUnOW2ZKRUjBSD5Ok3W78IXhGxxAEF90=
github.com/yuin/goldmark-emoji v1.0.4/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
go.jolheiser.com/tailroute v0.0.0-20240726150858-67ef456b46b5 h1:SJ4Y1f52dpC7c9Svl7dfwd2upkhB66cr8/l1TDTAUY4=
go.jolheiser.com/tailroute v0.0.0-20240726150858-67ef456b46b5/go.mod h1:CVL6F82hiqQ849aFeDZl1YolqiEKyY24oxxfjy8bZAc=
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a h1:8qmSSA8Gz/1kTrCe0nqR0R3Gb/NDhykzWw2q2mWZydM=
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@ -410,21 +207,10 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 h1:/8/t5pz/mgdRXhYOIeqqYhFAQLE4DDGegc0Y4ZjyFJM=
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8=
honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
tailscale.com v1.68.2 h1:nxy9HTAXPjuTbu/xzF05mS/v9ABMRGGJdPWEScTJxUo=
tailscale.com v1.68.2/go.mod h1:uqtoDEA8tw5+S+HLGqQGfpQsqeVtBS/EVVv5mXIaAoQ=
maragu.dev/gomponents v1.0.0 h1:eeLScjq4PqP1l+r5z/GC+xXZhLHXa6RWUWGW7gSfLh4=
maragu.dev/gomponents v1.0.0/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM=

View File

@ -1,399 +0,0 @@
schema = 3
[mod]
[mod."dario.cat/mergo"]
version = "v1.0.0"
hash = "sha256-jlpc8dDj+DmiOU4gEawBu8poJJj9My0s9Mvuk9oS8ww="
[mod."filippo.io/edwards25519"]
version = "v1.1.0"
hash = "sha256-9ACANrgWZSd5HYPfDZHY8DVbPSC9LOMgy8deq3rDOoc="
[mod."github.com/Microsoft/go-winio"]
version = "v0.6.1"
hash = "sha256-BL0BVaHtmPKQts/711W59AbHXjGKqFS4ZTal0RYnR9I="
[mod."github.com/ProtonMail/go-crypto"]
version = "v1.0.0"
hash = "sha256-Gflazvyv+457FpUTtPafJ+SdolYSalpsU0tragTxNi8="
[mod."github.com/a-h/templ"]
version = "v0.2.543"
hash = "sha256-1BvIj9UPZJp8SOXMPIGdHyZLIvjORHg2UY3pRZJM01s="
[mod."github.com/akutz/memconn"]
version = "v0.1.0"
hash = "sha256-WOs/RQYjQbn41ui8ztHgWSd4jxydE+fvIrl/0dmHD9Y="
[mod."github.com/alecthomas/chroma/v2"]
version = "v2.12.0"
hash = "sha256-w3gKGPwsoayknuU4ifPaF0JOMNqnKjIEutbIkR9c2Ag="
[mod."github.com/alexbrainman/sspi"]
version = "v0.0.0-20231016080023-1a75b4708caa"
hash = "sha256-Joa/NfBofK7lRlknQ5LVYD4M/dpd9xaCqsvYUBhKW0I="
[mod."github.com/anmitsu/go-shlex"]
version = "v0.0.0-20200514113438-38f4b401e2be"
hash = "sha256-L3Ak4X2z7WXq7vMKuiHCOJ29nlpajUQ08Sfb9T0yP54="
[mod."github.com/aws/aws-sdk-go-v2"]
version = "v1.24.1"
hash = "sha256-qZDUrodVDWWtdQ6zYuNHxpXt2quK56pPoZsKdRGmYzk="
[mod."github.com/aws/aws-sdk-go-v2/config"]
version = "v1.26.5"
hash = "sha256-RAXQlnUAsxgi2BndS+Uh2Cw+TCbtUx9BB0Tqqz50rLE="
[mod."github.com/aws/aws-sdk-go-v2/credentials"]
version = "v1.16.16"
hash = "sha256-qjtE3/VxNAEtS68nLXULgjO+LO3EkayGkr2iU300xdA="
[mod."github.com/aws/aws-sdk-go-v2/feature/ec2/imds"]
version = "v1.14.11"
hash = "sha256-j8LHZYM+rBmWHV1PT/H6lDL/3vgBqvQxoY2bVuOi7zo="
[mod."github.com/aws/aws-sdk-go-v2/internal/configsources"]
version = "v1.2.10"
hash = "sha256-fhrws72g+ecgXnkiip0GQ206XfVdxDA7/Rz9X3nvgd8="
[mod."github.com/aws/aws-sdk-go-v2/internal/endpoints/v2"]
version = "v2.5.10"
hash = "sha256-Rbj+matn96a/3Nj8EpLgrTycSV+331+WWJd5Lpk1LlQ="
[mod."github.com/aws/aws-sdk-go-v2/internal/ini"]
version = "v1.7.2"
hash = "sha256-6ev22rRSz4TuoX1AoCDr67qA15kg+cHMuQeTXwp8AFg="
[mod."github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding"]
version = "v1.10.4"
hash = "sha256-Wvbil3pNKlCyn0SWNXCobxzJGeE+2Eb7GNP2OZATTfY="
[mod."github.com/aws/aws-sdk-go-v2/service/internal/presigned-url"]
version = "v1.10.10"
hash = "sha256-vUSnrZzo3mzfZsthjjZRmGNrXkQ4D2B7Glzu66N5MrQ="
[mod."github.com/aws/aws-sdk-go-v2/service/ssm"]
version = "v1.44.7"
hash = "sha256-U7XGN5O0Ksp6O3a2Nkb0WinTwuIHjCw5UNSwPjrMo8k="
[mod."github.com/aws/aws-sdk-go-v2/service/sso"]
version = "v1.18.7"
hash = "sha256-+1q43+9Cx/K3/T+LJJLV1h2PLHsG/YKHUSgwC5r3uNQ="
[mod."github.com/aws/aws-sdk-go-v2/service/ssooidc"]
version = "v1.21.7"
hash = "sha256-aoolSWOU3Sb3weV2yf2wACuh3fYaUmyGXCZ2dTvcg4A="
[mod."github.com/aws/aws-sdk-go-v2/service/sts"]
version = "v1.26.7"
hash = "sha256-0LGfjSa65wNKuV/TH66XfN/uF4lZF9bSszbfTFWcLjY="
[mod."github.com/aws/smithy-go"]
version = "v1.19.0"
hash = "sha256-KsQMOHJFIXRF+8c4WxiXA/u1Ok6hEF6eylvnfdRGivg="
[mod."github.com/aymanbagabas/go-osc52/v2"]
version = "v2.0.1"
hash = "sha256-6Bp0jBZ6npvsYcKZGHHIUSVSTAMEyieweAX2YAKDjjg="
[mod."github.com/bits-and-blooms/bitset"]
version = "v1.13.0"
hash = "sha256-1jccFEH9In72Jt3wSn4vi4/MJd/UDfDrC9DlmsGVc9o="
[mod."github.com/charmbracelet/bubbletea"]
version = "v0.25.0"
hash = "sha256-A0WjFRFAUhwO3m7uvCOeefPPIM8ReU+xTtIRxG0aH+Y="
[mod."github.com/charmbracelet/keygen"]
version = "v0.5.0"
hash = "sha256-JFD2SdFL7tq3oVhnBEgiBTrJvjqdUtIuodAJuSFcJoA="
[mod."github.com/charmbracelet/lipgloss"]
version = "v0.9.1"
hash = "sha256-AHbabOymgDRIXsMBgJHS25/GgBWT54oGbd15EBWKeZc="
[mod."github.com/charmbracelet/log"]
version = "v0.3.1"
hash = "sha256-Er60POPID2eNrRZnBHxoI4yHn0mIKnXYftGKSslbXx0="
[mod."github.com/charmbracelet/ssh"]
version = "v0.0.0-20240201134204-3f297de25560"
hash = "sha256-r4h4bym47rs3C2us+sCgVfwAl4TCbm3bDCTsXKYREz8="
[mod."github.com/charmbracelet/wish"]
version = "v1.3.0"
hash = "sha256-3Uq1PDu5DMoWgJykFx/roGk20x8jdb7o5JFPpmEtX/c="
[mod."github.com/charmbracelet/x/errors"]
version = "v0.0.0-20240130180102-bafe6fbaee60"
hash = "sha256-GO8hf0lhVtl00C+xoTzvBtPU2cO0PymSLc2szBRUNtE="
[mod."github.com/charmbracelet/x/exp/term"]
version = "v0.0.0-20240130180102-bafe6fbaee60"
hash = "sha256-hEj/Gj1U1ahk5EFVZVAL52yrdBNO47yXykpiehJICbc="
[mod."github.com/cloudflare/circl"]
version = "v1.3.7"
hash = "sha256-AkOpcZ+evLxLJStvvr01+TLeWDqcLxY3e/AhGggzh40="
[mod."github.com/containerd/console"]
version = "v1.0.4-0.20230706203907-8f6c4e4faef5"
hash = "sha256-mxRERsgS6TmI5I0UYblhzl2FZlbtkJhUkfF1x6mZINw="
[mod."github.com/coreos/go-iptables"]
version = "v0.7.1-0.20240112124308-65c67c9f46e6"
hash = "sha256-kjnry8ld5Keew5q+tX7GSdGVZaXlehs2Lt+z8Rokhns="
[mod."github.com/creack/pty"]
version = "v1.1.21"
hash = "sha256-pjGw6wQlrVhN65XaIxZueNJqnXThGu00u24rKOLzxS0="
[mod."github.com/cyphar/filepath-securejoin"]
version = "v0.2.4"
hash = "sha256-heCD0xMxlwnHCHcRBgTjVexHOLyWI2zRW3E8NFKoLzk="
[mod."github.com/dblohm7/wingoes"]
version = "v0.0.0-20240119213807-a09d6be7affa"
hash = "sha256-zUj7jSZQiX53+4OyRq7LaQXp8nx8+JRpyT6IFdktyw0="
[mod."github.com/digitalocean/go-smbios"]
version = "v0.0.0-20180907143718-390a4f403a8e"
hash = "sha256-Hgx1ML3rigdYXx5ntnEYD5JEk6kJv1cL0+/GDbnPkbo="
[mod."github.com/dlclark/regexp2"]
version = "v1.10.0"
hash = "sha256-Jxzj/O/Q9tIWBOOgCkCibhrgJBzzfVIxYDsabt7O8ow="
[mod."github.com/dustin/go-humanize"]
version = "v1.0.1"
hash = "sha256-yuvxYYngpfVkUg9yAmG99IUVmADTQA0tMbBXe0Fq0Mc="
[mod."github.com/emirpasic/gods"]
version = "v1.18.1"
hash = "sha256-hGDKddjLj+5dn2woHtXKUdd49/3xdsqnhx7VEdCu1m4="
[mod."github.com/fxamacker/cbor/v2"]
version = "v2.5.0"
hash = "sha256-2rhvuNZjeidDP+Og6Q0u4ROiaPrEOjoI5wCb17XRnOQ="
[mod."github.com/gaissmai/bart"]
version = "v0.4.1"
hash = "sha256-Zz+8yx+y6QtF2raJo01ldzKIlqfxLX3eqLr6ub+Byzw="
[mod."github.com/go-chi/chi/v5"]
version = "v5.0.11"
hash = "sha256-95LKg/OVzhik2HUz6cirHH3eAT4qbHSg52bSvkc+XOY="
[mod."github.com/go-chi/httplog/v2"]
version = "v2.1.1"
hash = "sha256-bMpoHUSNk3Uds9NfrStwhDsdCONR4pJso9sVUhqfidk="
[mod."github.com/go-git/gcfg"]
version = "v1.5.1-0.20230307220236-3a3c6141e376"
hash = "sha256-f4k0gSYuo0/q3WOoTxl2eFaj7WZpdz29ih6CKc8Ude8="
[mod."github.com/go-git/go-billy/v5"]
version = "v5.5.0"
hash = "sha256-4XUoD2bOCMCdu83egb/y8kY/Fm0s1rWgPMtiahh38OQ="
[mod."github.com/go-git/go-git/v5"]
version = "v5.11.0"
hash = "sha256-2yUM/FlV+nYxacVynJCnDZeMub4Iu8JL2WBHmlnwOkE="
[mod."github.com/go-json-experiment/json"]
version = "v0.0.0-20231102232822-2e55bd4e08b0"
hash = "sha256-ltOLec3r0dGA/k1TTrQjuMuHxdxBlALkfLbdKuUNvmg="
[mod."github.com/go-logfmt/logfmt"]
version = "v0.6.0"
hash = "sha256-RtIG2qARd5sT10WQ7F3LR8YJhS8exs+KiuUiVf75bWg="
[mod."github.com/go-ole/go-ole"]
version = "v1.3.0"
hash = "sha256-tF8t3VcV71jQ4jbPL91BwR59AKDpUAFV1waIKzkXJu8="
[mod."github.com/godbus/dbus/v5"]
version = "v5.1.1-0.20230522191255-76236955d466"
hash = "sha256-OdcyeoGQ4xWUpl21WKXkbAm8yZHMDp8giY02arupjb4="
[mod."github.com/golang/groupcache"]
version = "v0.0.0-20210331224755-41bb18bfe9da"
hash = "sha256-7Gs7CS9gEYZkbu5P4hqPGBpeGZWC64VDwraSKFF+VR0="
[mod."github.com/google/btree"]
version = "v1.1.2"
hash = "sha256-K7V2obq3pLM71Mg0vhhHtZ+gtaubwXPQx3xcIyZDCjM="
[mod."github.com/google/go-cmp"]
version = "v0.6.0"
hash = "sha256-qgra5jze4iPGP0JSTVeY5qV5AvEnEu39LYAuUCIkMtg="
[mod."github.com/google/nftables"]
version = "v0.2.1-0.20240414091927-5e242ec57806"
hash = "sha256-OXMzx4Exf++E4UzCUE1gw3TOrfyI187cFu3cDZ9ifKU="
[mod."github.com/google/uuid"]
version = "v1.6.0"
hash = "sha256-VWl9sqUzdOuhW0KzQlv0gwwUQClYkmZwSydHG2sALYw="
[mod."github.com/gorilla/csrf"]
version = "v1.7.2"
hash = "sha256-pzOWbw8AOjhdRJYKlPBfh1j9a8TGNxgrlS4AmZpE1s8="
[mod."github.com/gorilla/securecookie"]
version = "v1.1.2"
hash = "sha256-KeMHNM9emxX+N0WYiZsTii7n8sNsmjWwbnQ9SaJfTKE="
[mod."github.com/hdevalence/ed25519consensus"]
version = "v0.2.0"
hash = "sha256-KTbeKMOT/HCJjDHqyciQjJPPgpNk6H0VyQCCbeGgs7Y="
[mod."github.com/illarion/gonotify"]
version = "v1.0.1"
hash = "sha256-73KDUfk5iNo12u+nNN2H4UTfJ1FlLifahnFNPzV1P/k="
[mod."github.com/insomniacslk/dhcp"]
version = "v0.0.0-20231206064809-8c70d406f6d2"
hash = "sha256-PHiso7HGNjCGbGdZ7TMGnSGzPDUTpvWpqAccoKPAaWM="
[mod."github.com/jbenet/go-context"]
version = "v0.0.0-20150711004518-d14ea06fba99"
hash = "sha256-VANNCWNNpARH/ILQV9sCQsBWgyL2iFT+4AHZREpxIWE="
[mod."github.com/jmespath/go-jmespath"]
version = "v0.4.0"
hash = "sha256-xpT9g2qIXmPq7eeHUXHiDqJeQoHCudh44G/KCSFbcuo="
[mod."github.com/josharian/native"]
version = "v1.1.1-0.20230202152459-5c7d0dd6ab86"
hash = "sha256-dgyrLXuM55z8FAoUjyt5TDlzim6HfphWo5wx1/DHLwE="
[mod."github.com/jsimonetti/rtnetlink"]
version = "v1.4.0"
hash = "sha256-GnrF2yxgm/h1JSTN1PwI4ysTl+oY0AyWzf3Ni7LZqRM="
[mod."github.com/kevinburke/ssh_config"]
version = "v1.2.0"
hash = "sha256-Ta7ZOmyX8gG5tzWbY2oES70EJPfI90U7CIJS9EAce0s="
[mod."github.com/klauspost/compress"]
version = "v1.17.4"
hash = "sha256-5E7dDtDKfL3jy7zJxHBMV57WlHZrP/OoEX5e6cOPba0="
[mod."github.com/kortschak/wol"]
version = "v0.0.0-20200729010619-da482cc4850a"
hash = "sha256-lnr9r/KNv4EeeNohFImC3Vd5E9nJ0N+4ZZ0VHFjwHps="
[mod."github.com/lucasb-eyer/go-colorful"]
version = "v1.2.0"
hash = "sha256-Gg9dDJFCTaHrKHRR1SrJgZ8fWieJkybljybkI9x0gyE="
[mod."github.com/mattn/go-isatty"]
version = "v0.0.20"
hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ="
[mod."github.com/mattn/go-localereader"]
version = "v0.0.1"
hash = "sha256-JlWckeGaWG+bXK8l8WEdZqmSiTwCA8b1qbmBKa/Fj3E="
[mod."github.com/mattn/go-runewidth"]
version = "v0.0.15"
hash = "sha256-WP39EU2UrQbByYfnwrkBDoKN7xzXsBssDq3pNryBGm0="
[mod."github.com/mdlayher/genetlink"]
version = "v1.3.2"
hash = "sha256-pgwXkyDY1dlB8tmV1lQ0Bz/2g0zmJOyXvQjacACy924="
[mod."github.com/mdlayher/netlink"]
version = "v1.7.2"
hash = "sha256-08qlkKvG1+XFY3MDx7M1jdvaDMsmF9qv5/xMNaw3snc="
[mod."github.com/mdlayher/sdnotify"]
version = "v1.0.0"
hash = "sha256-O7MJt6Bam2bUoede07Z1T6EtW+kCJ+/4dJtYopZct1s="
[mod."github.com/mdlayher/socket"]
version = "v0.5.0"
hash = "sha256-3zEbix66G+LQ2xAQW9XmkyubK9R+Vwnm09H5fqqLC7w="
[mod."github.com/miekg/dns"]
version = "v1.1.58"
hash = "sha256-UGvyC1Abh2S5VaAUCV9AUuDMrCvpiWQy/UnYM9DfIB8="
[mod."github.com/mitchellh/go-ps"]
version = "v1.0.0"
hash = "sha256-HzxVHNLHZpnsBuPcub0G+9jjDcDOsxM/6wifbsxf7EY="
[mod."github.com/muesli/ansi"]
version = "v0.0.0-20230316100256-276c6243b2f6"
hash = "sha256-qRKn0Bh2yvP0QxeEMeZe11Vz0BPFIkVcleKsPeybKMs="
[mod."github.com/muesli/cancelreader"]
version = "v0.2.2"
hash = "sha256-uEPpzwRJBJsQWBw6M71FDfgJuR7n55d/7IV8MO+rpwQ="
[mod."github.com/muesli/reflow"]
version = "v0.3.0"
hash = "sha256-Pou2ybE9SFSZG6YfZLVV1Eyfm+X4FuVpDPLxhpn47Cc="
[mod."github.com/muesli/termenv"]
version = "v0.15.2"
hash = "sha256-Eum/SpyytcNIchANPkG4bYGBgcezLgej7j/+6IhqoMU="
[mod."github.com/peterbourgon/ff/v3"]
version = "v3.4.0"
hash = "sha256-rmRl4GSmc2atnMbw6hTs6jwxW5lO4ivYuF2VN3jacZM="
[mod."github.com/pierrec/lz4/v4"]
version = "v4.1.21"
hash = "sha256-u47Lm4tN2ChGDLGyR+Jpi/Mi0bOFBVT6PTpPFdu2rMU="
[mod."github.com/pjbgf/sha1cd"]
version = "v0.3.0"
hash = "sha256-kX9BdLh2dxtGNaDvc24NORO+C0AZ7JzbrXrtecCdB7w="
[mod."github.com/prometheus-community/pro-bing"]
version = "v0.4.0"
hash = "sha256-3TH0wB85OITw3uzTcEva2EcEF6jNf98sAoSOsnL2G9g="
[mod."github.com/rivo/uniseg"]
version = "v0.4.6"
hash = "sha256-zGfzO8FWj03POzo47SzrK1B4yLMKJ7iic6ium76ZLzI="
[mod."github.com/safchain/ethtool"]
version = "v0.3.0"
hash = "sha256-q5bQGHB7cyEejA9tQkrhpvzpfYRvXcmClbWBgfs3Ymc="
[mod."github.com/sergi/go-diff"]
version = "v1.3.1"
hash = "sha256-XLA/BLIPuUU76yikXqIeRSXr7T7A3Uz6I27+mDxGj7w="
[mod."github.com/skeema/knownhosts"]
version = "v1.2.1"
hash = "sha256-u0jB6ahTdGa+SvcIvPNRLnbSHvgmW9X/ThRq0nWQrJs="
[mod."github.com/tailscale/certstore"]
version = "v0.1.1-0.20231202035212-d3fa0460f47e"
hash = "sha256-Q0HLTQPRsIizRqvdBBaLGoLascQiSpQm+X3NW3ytpDQ="
[mod."github.com/tailscale/go-winio"]
version = "v0.0.0-20231025203758-c4f33415bf55"
hash = "sha256-WFW20c02gIk2MhafeVVNydmSfszPZegshDd2Y5abGgY="
[mod."github.com/tailscale/golang-x-crypto"]
version = "v0.0.0-20240604161659-3fde5e568aa4"
hash = "sha256-vIrSd0emexe1lcjJqLbEtg7/TaZDnpGrDs96mxjANyE="
[mod."github.com/tailscale/goupnp"]
version = "v1.0.1-0.20210804011211-c64d0f06ea05"
hash = "sha256-PRMJcYY+wjpbHkC9dQWTPErh2ID29UnFxehxmYR704I="
[mod."github.com/tailscale/hujson"]
version = "v0.0.0-20221223112325-20486734a56a"
hash = "sha256-q9c2IjwbL0IgYPznVpQP1XT/8lrzH1niI3E+wjNcf5Y="
[mod."github.com/tailscale/netlink"]
version = "v1.1.1-0.20211101221916-cabfb018fe85"
hash = "sha256-bMK5qqObMcxIbPy5XhOZP8TGDaXvDAHsE0FR68VMLi8="
[mod."github.com/tailscale/peercred"]
version = "v0.0.0-20240214030740-b535050b2aa4"
hash = "sha256-82cdwvTUr47qMP2MHiMdY5O8vr/hA2aJ/KW2VTu97KI="
[mod."github.com/tailscale/web-client-prebuilt"]
version = "v0.0.0-20240226180453-5db17b287bf1"
hash = "sha256-iE8PCr0At4P75cG7dDa6fwhARJUIUw9pwvgtQNMOjAA="
[mod."github.com/tailscale/wireguard-go"]
version = "v0.0.0-20240429185444-03c5a0ccf754"
hash = "sha256-XhfEG8esndgZDoI2AeRvs95XRYiIrj/0NiVDBpOcacY="
[mod."github.com/tcnksm/go-httpstat"]
version = "v0.2.0"
hash = "sha256-bCWn8E+DcZY6+yPu07AF3hCcDZx3CFdD74qfpDIgVqI="
[mod."github.com/u-root/u-root"]
version = "v0.12.0"
hash = "sha256-B9Qoq1S0l0W6twET54uxiWjh2ulxN/zMLAeWJX4cXW0="
[mod."github.com/u-root/uio"]
version = "v0.0.0-20240118234441-a3c409a6018e"
hash = "sha256-rUjQMG+HprbgHKzrIPFrQ6S3BCEyYQyUbA/D72mY8iU="
[mod."github.com/vishvananda/netlink"]
version = "v1.2.1-beta.2"
hash = "sha256-ePReedgYT0KuAx/HOUgG76zDZ2XpW/u8bGlsWysxKzE="
[mod."github.com/vishvananda/netns"]
version = "v0.0.4"
hash = "sha256-tEba2cxyk3GdCYvEIttQ8aZCzHcB0ZiUt6fUEARDkWU="
[mod."github.com/x448/float16"]
version = "v0.8.4"
hash = "sha256-VKzMTMS9pIB/cwe17xPftCSK9Mf4Y6EuBEJlB4by5mE="
[mod."github.com/xanzy/ssh-agent"]
version = "v0.3.3"
hash = "sha256-l3pGB6IdzcPA/HLk93sSN6NM2pKPy+bVOoacR5RC2+c="
[mod."github.com/yuin/goldmark"]
version = "v1.6.0"
hash = "sha256-0PeGjGxxM7lUSx2dn8yPUBpilPQzEN9nkgf3s+5zGTY="
[mod."github.com/yuin/goldmark-emoji"]
version = "v1.0.2"
hash = "sha256-RvzhNXlF98fu9SD/Rve9JMtR4bcRh7rN56Twhh/kmt4="
[mod."github.com/yuin/goldmark-highlighting/v2"]
version = "v2.0.0-20230729083705-37449abec8cc"
hash = "sha256-HpiwU7jIeDUAg2zOpTIiviQir8dpRPuXYh2nqFFccpg="
[mod."go.jolheiser.com/tailroute"]
version = "v0.0.0-20240726150858-67ef456b46b5"
hash = "sha256-NJBg8m3ukwBycuXj3lUtTclX1kmdw3xBW+YCKnPuU8g="
[mod."go4.org/mem"]
version = "v0.0.0-20220726221520-4f986261bf13"
hash = "sha256-H2Fsuvzbqp/6JKzC03XPTQTSQBcGs+B5VGsBjrQDY3c="
[mod."go4.org/netipx"]
version = "v0.0.0-20231129151722-fdeea329fbba"
hash = "sha256-qFrVlacz5R3Lpkzqeg1/1MYew3DJzY9hG0Uh/ua+SOU="
[mod."golang.org/x/crypto"]
version = "v0.21.0"
hash = "sha256-Z4k1LvFh4Jai7HUe6TTuXSG3VnuiRpMwdARIdZZqSYk="
[mod."golang.org/x/exp"]
version = "v0.0.0-20240119083558-1b970713d09a"
hash = "sha256-JQ3JLywTjgboNhs12blhOkS3ty7m8sUa/zaWv1k/X28="
[mod."golang.org/x/mod"]
version = "v0.16.0"
hash = "sha256-aN1Cz5Wqd9YCjK8nFW6JWn+n1HfFoEcgYZmGO/FYtbw="
[mod."golang.org/x/net"]
version = "v0.23.0"
hash = "sha256-ZB4504rtgsHbcRfijjlqt4/2ddb8tyQB5IBn126uVTQ="
[mod."golang.org/x/sync"]
version = "v0.6.0"
hash = "sha256-LLims/wjDZtIqlYCVHREewcUOX4hwRwplEuZKPOJ/HI="
[mod."golang.org/x/sys"]
version = "v0.19.0"
hash = "sha256-cmuL31TYLJmDm/fDnI2Sn0wB88cpdOHV1+urorsJWx4="
[mod."golang.org/x/term"]
version = "v0.18.0"
hash = "sha256-lpze9arFZIhBV8Ht3VZyoiUwqPkeH2IwfXt8M3xljiM="
[mod."golang.org/x/text"]
version = "v0.14.0"
hash = "sha256-yh3B0tom1RfzQBf1RNmfdNWF1PtiqxV41jW1GVS6JAg="
[mod."golang.org/x/time"]
version = "v0.5.0"
hash = "sha256-W6RgwgdYTO3byIPOFxrP2IpAZdgaGowAaVfYby7AULU="
[mod."golang.org/x/tools"]
version = "v0.19.0"
hash = "sha256-Xf05Ao398gBzxn5C8H6x+XsLjFLIm+UUfpDekQYA0cw="
[mod."golang.zx2c4.com/wintun"]
version = "v0.0.0-20230126152724-0fa3db229ce2"
hash = "sha256-cjMLNjKnnupVROWmeASORVieAL9ieYdzX3cFzG8bCpo="
[mod."golang.zx2c4.com/wireguard/windows"]
version = "v0.5.3"
hash = "sha256-wcJWS/4Fqbc+1RHSntex0zBdlHiZfzrea4QabYkNKvU="
[mod."gopkg.in/warnings.v0"]
version = "v0.1.2"
hash = "sha256-ATVL9yEmgYbkJ1DkltDGRn/auGAjqGOfjQyBYyUo8s8="
[mod."gopkg.in/yaml.v2"]
version = "v2.4.0"
hash = "sha256-uVEGglIedjOIGZzHW4YwN1VoRSTK8o0eGZqzd+TNdd0="
[mod."gvisor.dev/gvisor"]
version = "v0.0.0-20240306221502-ee1e1f6070e3"
hash = "sha256-veCyTYMx5N/K7xU86GgQupjAjjiL3hlvIjz+jhBFM/0="
[mod."nhooyr.io/websocket"]
version = "v1.8.10"
hash = "sha256-EsUWUFIA2uJTap1DfsYuSxlPMH3UHDpxEohJMalDOcI="
[mod."tailscale.com"]
version = "v1.68.2"
hash = "sha256-GdYdZ/FA2m0dw5maZ9QFkiMo89UZtnzjUoWK+fLLV/c="

View File

@ -98,7 +98,7 @@ func (r Repo) Dir(ref, path string) ([]FileInfo, error) {
}
}
fis := make([]FileInfo, 0)
fis := make([]FileInfo, 0, len(t.Entries))
for _, entry := range t.Entries {
fm, err := entry.Mode.ToOSFileMode()
if err != nil {
@ -118,12 +118,30 @@ func (r Repo) Dir(ref, path string) ([]FileInfo, error) {
sort.Slice(fis, func(i, j int) bool {
fi1 := fis[i]
fi2 := fis[j]
return (fi1.IsDir && !fi2.IsDir) || fi1.Name() < fi2.Name()
if fi1.IsDir != fi2.IsDir {
return fi1.IsDir
}
return fi1.Name() < fi2.Name()
})
return fis, nil
}
// GetCommitFromRef returns the commit object for a given ref
func (r Repo) GetCommitFromRef(ref string) (*object.Commit, error) {
g, err := r.Git()
if err != nil {
return nil, err
}
hash, err := g.ResolveRevision(plumbing.Revision(ref))
if err != nil {
return nil, err
}
return g.CommitObject(*hash)
}
// FileContent returns the content of a file in the git tree at a given ref/rev
func (r Repo) FileContent(ref, file string) (string, error) {
t, err := r.Tree(ref)

View File

@ -0,0 +1,276 @@
package git_test
import (
"path/filepath"
"testing"
"time"
"github.com/alecthomas/assert/v2"
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
"go.jolheiser.com/ugit/internal/git"
)
func TestEnsureRepo(t *testing.T) {
tmp := t.TempDir()
ok, err := git.PathExists(filepath.Join(tmp, "test"))
assert.False(t, ok, "repo should not exist yet")
assert.NoError(t, err, "PathExists should not error when repo doesn't exist")
err = git.EnsureRepo(tmp, "test")
assert.NoError(t, err, "repo should be created")
ok, err = git.PathExists(filepath.Join(tmp, "test"))
assert.True(t, ok, "repo should exist")
assert.NoError(t, err, "EnsureRepo should not error when path exists")
err = git.EnsureRepo(tmp, "test")
assert.NoError(t, err, "repo should already exist")
}
func TestRepo(t *testing.T) {
tmp := t.TempDir()
err := git.EnsureRepo(tmp, "test.git")
assert.NoError(t, err, "should create repo")
repo, err := git.NewRepo(tmp, "test")
assert.NoError(t, err, "should init new repo")
assert.True(t, repo.Meta.Private, "repo should default to private")
repo.Meta.Private = false
err = repo.SaveMeta()
assert.NoError(t, err, "should save repo meta")
repo, err = git.NewRepo(tmp, "test")
assert.NoError(t, err, "should not error when getting existing repo")
assert.False(t, repo.Meta.Private, "repo should be public after saving meta")
}
func TestPathExists(t *testing.T) {
tmp := t.TempDir()
exists, err := git.PathExists(tmp)
assert.NoError(t, err)
assert.True(t, exists)
doesNotExist := filepath.Join(tmp, "does-not-exist")
exists, err = git.PathExists(doesNotExist)
assert.NoError(t, err)
assert.False(t, exists)
}
func TestRepoMetaUpdate(t *testing.T) {
original := git.RepoMeta{
Description: "Original description",
Private: true,
Tags: git.TagSet{"tag1": struct{}{}, "tag2": struct{}{}},
}
update := git.RepoMeta{
Description: "Updated description",
Private: false,
Tags: git.TagSet{"tag3": struct{}{}},
}
err := original.Update(update)
assert.NoError(t, err)
assert.Equal(t, "Updated description", original.Description)
assert.False(t, original.Private)
assert.Equal(t, []string{"tag1", "tag2", "tag3"}, original.Tags.Slice())
}
func TestFileInfoName(t *testing.T) {
testCases := []struct {
path string
expected string
}{
{path: "file.txt", expected: "file.txt"},
{path: "dir/file.txt", expected: "file.txt"},
{path: "nested/path/to/file.go", expected: "file.go"},
{path: "README.md", expected: "README.md"},
}
for _, tc := range testCases {
t.Run(tc.path, func(t *testing.T) {
fi := git.FileInfo{Path: tc.path}
assert.Equal(t, tc.expected, fi.Name())
})
}
}
func TestCommitSummaryAndDetails(t *testing.T) {
testCases := []struct {
message string
expectedSummary string
expectedDetails string
}{
{
message: "Simple commit message",
expectedSummary: "Simple commit message",
expectedDetails: "",
},
{
message: "Add feature X\n\nThis commit adds feature X\nWith multiple details\nAcross multiple lines",
expectedSummary: "Add feature X",
expectedDetails: "\nThis commit adds feature X\nWith multiple details\nAcross multiple lines",
},
{
message: "Fix bug\n\nDetailed explanation",
expectedSummary: "Fix bug",
expectedDetails: "\nDetailed explanation",
},
}
for _, tc := range testCases {
t.Run(tc.message, func(t *testing.T) {
commit := git.Commit{
SHA: "abcdef1234567890",
Message: tc.message,
Signature: "",
Author: "Test User",
Email: "test@example.com",
When: time.Now(),
}
assert.Equal(t, tc.expectedSummary, commit.Summary())
assert.Equal(t, tc.expectedDetails, commit.Details())
})
}
}
func TestCommitShort(t *testing.T) {
commit := git.Commit{
SHA: "abcdef1234567890abcdef1234567890",
}
assert.Equal(t, "abcdef12", commit.Short())
}
func TestCommitFilePath(t *testing.T) {
testCases := []struct {
name string
fromPath string
toPath string
expected string
}{
{
name: "to path preferred",
fromPath: "old/path.txt",
toPath: "new/path.txt",
expected: "new/path.txt",
},
{
name: "fallback to from path",
fromPath: "deleted/file.txt",
toPath: "",
expected: "deleted/file.txt",
},
{
name: "both paths empty",
fromPath: "",
toPath: "",
expected: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cf := git.CommitFile{
From: git.CommitFileEntry{Path: tc.fromPath},
To: git.CommitFileEntry{Path: tc.toPath},
}
assert.Equal(t, tc.expected, cf.Path())
})
}
}
func TestRepoName(t *testing.T) {
tmp := t.TempDir()
repoName := "testrepo"
err := git.EnsureRepo(tmp, repoName+".git")
assert.NoError(t, err)
repo, err := git.NewRepo(tmp, repoName)
assert.NoError(t, err)
assert.Equal(t, repoName, repo.Name())
repoName2 := "test-repo-with-hyphens"
err = git.EnsureRepo(tmp, repoName2+".git")
assert.NoError(t, err)
repo2, err := git.NewRepo(tmp, repoName2)
assert.NoError(t, err)
assert.Equal(t, repoName2, repo2.Name())
}
func TestHandlePushOptions(t *testing.T) {
tmp := t.TempDir()
err := git.EnsureRepo(tmp, "test.git")
assert.NoError(t, err)
repo, err := git.NewRepo(tmp, "test")
assert.NoError(t, err)
opts := []*packp.Option{
{Key: "description", Value: "New description"},
}
err = git.HandlePushOptions(repo, opts)
assert.NoError(t, err)
assert.Equal(t, "New description", repo.Meta.Description)
opts = []*packp.Option{
{Key: "private", Value: "false"},
}
err = git.HandlePushOptions(repo, opts)
assert.NoError(t, err)
assert.False(t, repo.Meta.Private)
repo.Meta.Private = true
opts = []*packp.Option{
{Key: "private", Value: "invalid"},
}
err = git.HandlePushOptions(repo, opts)
assert.NoError(t, err)
assert.True(t, repo.Meta.Private)
opts = []*packp.Option{
{Key: "tags", Value: "tag1,tag2"},
}
err = git.HandlePushOptions(repo, opts)
assert.NoError(t, err)
opts = []*packp.Option{
{Key: "description", Value: "Combined update"},
{Key: "private", Value: "true"},
}
err = git.HandlePushOptions(repo, opts)
assert.NoError(t, err)
assert.Equal(t, "Combined update", repo.Meta.Description)
assert.True(t, repo.Meta.Private)
}
func TestRepoPath(t *testing.T) {
tmp := t.TempDir()
err := git.EnsureRepo(tmp, "test.git")
assert.NoError(t, err)
repo, err := git.NewRepo(tmp, "test")
assert.NoError(t, err)
expected := filepath.Join(tmp, "test.git")
assert.Equal(t, expected, repo.Path())
}
func TestEnsureJSONFile(t *testing.T) {
tmp := t.TempDir()
err := git.EnsureRepo(tmp, "test.git")
assert.NoError(t, err)
repo, err := git.NewRepo(tmp, "test")
assert.NoError(t, err)
assert.True(t, repo.Meta.Private, "default repo should be private")
assert.Equal(t, "", repo.Meta.Description, "default description should be empty")
assert.Equal(t, 0, len(repo.Meta.Tags), "default tags should be empty")
}

View File

@ -18,8 +18,10 @@ type GrepResult struct {
// Grep performs a naive "code search" via git grep
func (r Repo) Grep(search string) ([]GrepResult, error) {
// Plain-text search only
re, err := regexp.Compile(regexp.QuoteMeta(search))
if strings.HasPrefix(search, "=") {
search = regexp.QuoteMeta(strings.TrimPrefix(search, "="))
}
re, err := regexp.Compile(search)
if err != nil {
return nil, err
}

View File

@ -3,16 +3,67 @@ package git
import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"slices"
)
// RepoMeta is the meta information a Repo can have
type RepoMeta struct {
Description string `json:"description"`
Private bool `json:"private"`
Tags []string `json:"tags"`
Tags TagSet `json:"tags"`
}
// TagSet is a Set of tags
type TagSet map[string]struct{}
// Add adds a tag to the set
func (t TagSet) Add(tag string) {
t[tag] = struct{}{}
}
// Remove removes a tag from the set
func (t TagSet) Remove(tag string) {
delete(t, tag)
}
// Contains checks if a tag is in the set
func (t TagSet) Contains(tag string) bool {
_, ok := t[tag]
return ok
}
// Slice returns the set as a (sorted) slice
func (t TagSet) Slice() []string {
s := make([]string, 0, len(t))
for k := range t {
s = append(s, k)
}
slices.Sort(s)
return s
}
// MarshalJSON implements [json.Marshaler]
func (t TagSet) MarshalJSON() ([]byte, error) {
return json.Marshal(t.Slice())
}
// UnmarshalJSON implements [json.Unmarshaler]
func (t *TagSet) UnmarshalJSON(b []byte) error {
var s []string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
if *t == nil {
*t = make(TagSet)
}
for _, ss := range s {
t.Add(ss)
}
return nil
}
// Update updates meta given another RepoMeta
@ -46,6 +97,16 @@ func (r Repo) SaveMeta() error {
return json.NewEncoder(fi).Encode(r.Meta)
}
var defaultMeta = func() []byte {
b, err := json.Marshal(RepoMeta{
Private: true,
})
if err != nil {
panic(fmt.Sprintf("could not init default meta: %v", err))
}
return b
}()
func ensureJSONFile(path string) error {
_, err := os.Stat(path)
if err == nil {
@ -59,7 +120,7 @@ func ensureJSONFile(path string) error {
return err
}
defer fi.Close()
if _, err := fi.WriteString(`{"private":true}`); err != nil {
if _, err := fi.Write(defaultMeta); err != nil {
return err
}
return nil

View File

@ -0,0 +1,53 @@
package git
import (
"encoding/json"
"testing"
"github.com/alecthomas/assert/v2"
)
func TestTagSet(t *testing.T) {
set := make(TagSet)
assert.Equal(t, 0, len(set))
assert.Equal(t, 0, len(set.Slice()))
set.Add("foo")
assert.Equal(t, 1, len(set))
assert.Equal(t, 1, len(set.Slice()))
assert.True(t, set.Contains("foo"))
set.Add("bar")
assert.Equal(t, 2, len(set))
assert.Equal(t, 2, len(set.Slice()))
assert.True(t, set.Contains("foo"))
assert.True(t, set.Contains("bar"))
set.Add("bar")
assert.Equal(t, 2, len(set))
assert.Equal(t, 2, len(set.Slice()))
assert.True(t, set.Contains("foo"))
assert.True(t, set.Contains("bar"))
set.Remove("foo")
assert.Equal(t, 1, len(set))
assert.Equal(t, 1, len(set.Slice()))
assert.False(t, set.Contains("foo"))
assert.True(t, set.Contains("bar"))
set.Add("foo")
set.Add("baz")
j, err := json.Marshal(set)
assert.NoError(t, err)
assert.Equal(t, `["bar","baz","foo"]`, string(j))
set = make(TagSet)
b := []byte(`["foo","bar","baz"]`)
err = json.Unmarshal(b, &set)
assert.NoError(t, err)
assert.Equal(t, 3, len(set))
assert.Equal(t, 3, len(set.Slice()))
assert.True(t, set.Contains("foo"))
assert.True(t, set.Contains("bar"))
assert.True(t, set.Contains("baz"))
}

View File

@ -58,15 +58,11 @@ func HandlePushOptions(repo *Repo, opts []*packp.Option) error {
remove = true
tagValue = strings.TrimPrefix(tagValue, "-")
}
for idx, tag := range repo.Meta.Tags {
if strings.EqualFold(tag, tagValue) {
tagValue = strings.ToLower(tagValue)
if remove {
repo.Meta.Tags = append(repo.Meta.Tags[:idx], repo.Meta.Tags[idx+1:]...)
repo.Meta.Tags.Remove(tagValue)
} else {
repo.Meta.Tags = append(repo.Meta.Tags, strings.ToLower(tagValue))
}
break
}
repo.Meta.Tags.Add(tagValue)
}
}
}

View File

@ -58,7 +58,6 @@ func gitService(ctx ReadWriteContexter, command, repoDir string, args ...string)
cmd.Env = append(os.Environ(), fmt.Sprintf("UGIT_REPODIR=%s", repoDir), "GIT_PROTOCOL=version=2")
cmd.Stdin = ctx
cmd.Stdout = ctx
fmt.Println(cmd.Env, cmd.String())
return cmd.Run()
}

View File

@ -57,6 +57,9 @@ func NewRepo(dir, name string) (*Repo, error) {
if err := json.NewDecoder(fi).Decode(&r.Meta); err != nil {
return nil, err
}
if r.Meta.Tags == nil {
r.Meta.Tags = make(TagSet)
}
return r, nil
}
@ -134,6 +137,14 @@ type CommitFileEntry struct {
Commit string
}
// Path returns either the To or From path, in order of preference
func (c CommitFile) Path() string {
if c.To.Path != "" {
return c.To.Path
}
return c.From.Path
}
// Short returns the first eight characters of the SHA
func (c Commit) Short() string {
return c.SHA[:8]

View File

@ -0,0 +1,61 @@
package git
import (
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
)
// ListRepos returns all directory entries in the given directory
func ListRepos(dir string) ([]fs.DirEntry, error) {
entries, err := os.ReadDir(dir)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return []fs.DirEntry{}, nil
}
return nil, err
}
return entries, nil
}
// DeleteRepo deletes a git repository from the filesystem
func DeleteRepo(repoPath string) error {
return os.RemoveAll(repoPath)
}
// RenameRepo renames a git repository
func RenameRepo(repoDir, oldName, newName string) error {
if !filepath.IsAbs(repoDir) {
return errors.New("repository directory must be an absolute path")
}
if !filepath.IsAbs(oldName) && !filepath.IsAbs(newName) {
oldPath := filepath.Join(repoDir, oldName)
if !strings.HasSuffix(oldPath, ".git") {
oldPath += ".git"
}
newPath := filepath.Join(repoDir, newName)
if !strings.HasSuffix(newPath, ".git") {
newPath += ".git"
}
return os.Rename(oldPath, newPath)
}
return errors.New("repository names should not be absolute paths")
}
// RepoPathExists checks if a path exists
func RepoPathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if errors.Is(err, fs.ErrNotExist) {
return false, nil
}
return false, err
}

View File

@ -0,0 +1,38 @@
package html
import (
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/components"
. "maragu.dev/gomponents/html"
)
type BaseContext struct {
Title string
Description string
}
func base(bc BaseContext, children ...Node) Node {
return HTML5(HTML5Props{
Title: bc.Title,
Description: bc.Description,
Head: []Node{
Link(Rel("icon"), Href("/_/favicon.svg")),
Link(Rel("stylesheet"), Href("/_/tailwind.css")),
ogp("title", bc.Title),
ogp("description", bc.Description),
Meta(Name("forge"), Content("ugit")),
Meta(Name("keywords"), Content("git,forge,ugit")),
},
Body: []Node{
Class("latte dark:mocha bg-base/50 dark:bg-base/95 max-w-7xl mx-5 sm:mx-auto my-10"),
H2(Class("text-text text-xl mb-3"),
A(Class("text-text text-xl mb-3"), Href("/"), Text("Home")),
),
Group(children),
},
})
}
func ogp(property, content string) Node {
return El("meta", Attr("property", "og:"+property), Attr("content", content))
}

View File

@ -1,25 +0,0 @@
package html
type BaseContext struct {
Title string
Description string
}
templ base(bc BaseContext) {
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{ bc.Title }</title>
<link rel="icon" href="/_/favicon.svg"/>
<link rel="stylesheet" href="/_/tailwind.css"/>
<meta property="og:title" content={ bc.Title }/>
<meta property="og:description" content={ bc.Description }/>
</head>
<body class="latte dark:mocha bg-base/50 dark:bg-base/95 max-w-7xl mx-5 sm:mx-auto my-10">
<h2 class="text-text text-xl mb-3"><a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href="/">Home</a></h2>
{ children... }
</body>
</html>
}

View File

@ -1,87 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package html
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
type BaseContext struct {
Title string
Description string
}
func base(bc BaseContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(bc.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `base.templ`, Line: 14, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</title><link rel=\"icon\" href=\"/_/favicon.svg\"><link rel=\"stylesheet\" href=\"/_/tailwind.css\"><meta property=\"og:title\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(bc.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `base.templ`, Line: 17, Col: 47}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta property=\"og:description\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(bc.Description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `base.templ`, Line: 18, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></head><body class=\"latte dark:mocha bg-base/50 dark:bg-base/95 max-w-7xl mx-5 sm:mx-auto my-10\"><h2 class=\"text-text text-xl mb-3\"><a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"/\">Home</a></h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -47,7 +47,3 @@
.code>.chroma {
@apply text-sm p-3 rounded overflow-scroll;
}
.chroma .line {
@apply overflow-scroll
}

View File

@ -12,6 +12,7 @@ import (
"go.jolheiser.com/ugit/internal/html/markup"
"github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/styles"
)
@ -25,7 +26,6 @@ var (
otherCSS string
)
//go:generate templ generate
//go:generate go run generate.go
func main() {
if err := tailwind(); err != nil {
@ -33,7 +33,7 @@ func main() {
}
}
// Generate tailwind code from templ templates and combine with other misc CSS
// Generate tailwind code from templates and combine with other misc CSS
func tailwind() error {
fmt.Println("generating tailwind...")
@ -47,15 +47,16 @@ func tailwind() error {
}
fmt.Println("generating chroma styles...")
formatter := html.New(markup.Options("")...)
latte := styles.Get("catppuccin-latte")
if err := markup.Formatter.WriteCSS(tmp, latte); err != nil {
if err := formatter.WriteCSS(tmp, latte); err != nil {
return err
}
tmp.WriteString("@media (prefers-color-scheme: dark) {")
mocha := styles.Get("catppuccin-mocha")
if err := markup.Formatter.WriteCSS(tmp, mocha); err != nil {
if err := formatter.WriteCSS(tmp, mocha); err != nil {
return err
}
tmp.WriteString("}")

View File

@ -0,0 +1,105 @@
package html
import (
"fmt"
"github.com/dustin/go-humanize"
"go.jolheiser.com/ugit/assets"
"go.jolheiser.com/ugit/internal/git"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
type IndexContext struct {
BaseContext
Profile IndexProfile
Repos []*git.Repo
}
type IndexProfile struct {
Username string
Email string
Links []IndexLink
}
type IndexLink struct {
Name string
URL string
}
func lastCommitTime(repo *git.Repo, human bool) string {
c, err := repo.LastCommit()
if err != nil {
return ""
}
if human {
return humanize.Time(c.When)
}
return c.When.Format("01/02/2006 03:04:05 PM")
}
func lastCommit(repo *git.Repo) *git.Commit {
c, err := repo.LastCommit()
if err != nil {
return nil
}
return &c
}
func IndexTemplate(ic IndexContext) Node {
return base(ic.BaseContext, []Node{
Header(
H1(Class("text-text text-xl font-bold"), Text(ic.Title)),
H2(Class("text-subtext1 text-lg"), Text(ic.Description)),
),
Main(Class("mt-5"),
Div(Class("grid grid-cols-1 sm:grid-cols-8"),
If(ic.Profile.Username != "",
Div(Class("text-mauve"), Text("@"+ic.Profile.Username)),
),
If(ic.Profile.Email != "", Group([]Node{
Div(Class("text-mauve col-span-2"),
Div(Class("w-5 h-5 stroke-mauve inline-block mr-1 align-middle"), Raw(string(assets.EmailIcon))),
A(Class("underline decoration-mauve/50 decoration-dashed hover:decoration-solid"), Href("mailto:"+ic.Profile.Email), Text(ic.Profile.Email)),
),
}),
),
),
Div(Class("grid grid-cols-1 sm:grid-cols-8"),
Map(ic.Profile.Links, func(link IndexLink) Node {
return Div(Class("text-mauve"),
Div(Class("w-5 h-5 stroke-mauve inline-block mr-1 align-middle"),
Raw(string(assets.LinkIcon)),
),
A(Class("underline decoration-mauve/50 decoration-dashed hover:decoration-solid"), Rel("me"), Href(link.URL), Text(link.Name)),
)
}),
),
Div(Class("grid sm:grid-cols-10 gap-2 mt-5"),
Map(ic.Repos, func(repo *git.Repo) Node {
commit := lastCommit(repo)
return Group([]Node{
Div(Class("sm:col-span-2 text-blue dark:text-lavender"),
A(Class("underline decoration-blue/50 dark:decoration-lavender/50 decoration-dashed hover:decoration-solid"), Href("/"+repo.Name()), Text(repo.Name())),
),
Div(Class("sm:col-span-3 text-subtext0"), Text(repo.Meta.Description)),
Div(Class("sm:col-span-3 text-subtext0"),
If(commit != nil,
Div(Title(commit.Message),
A(Class("underline text-blue dark:text-lavender decoration-blue/50 dark:decoration-lavender/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/commit/%s", repo.Name(), commit.SHA)), Text(commit.Short())),
Text(": "+commit.Summary()),
),
),
),
Div(Class("sm:col-span-1 text-subtext0"),
Map(repo.Meta.Tags.Slice(), func(tag string) Node {
return A(Class("rounded border-rosewater border-solid border pb-0.5 px-1 mr-1 mb-1 inline-block"), Href("?tag="+tag), Text(tag))
}),
),
Div(Class("sm:col-span-1 text-text/80 mb-4 sm:mb-0"), Title(lastCommitTime(repo, false)), Text(lastCommitTime(repo, true))),
})
}),
),
),
}...)
}

View File

@ -1,79 +0,0 @@
package html
import "go.jolheiser.com/ugit/internal/git"
import "github.com/dustin/go-humanize"
import "go.jolheiser.com/ugit/assets"
type IndexContext struct {
BaseContext
Profile IndexProfile
Repos []*git.Repo
}
type IndexProfile struct {
Username string
Email string
Links []IndexLink
}
type IndexLink struct {
Name string
URL string
}
func lastCommit(repo *git.Repo, human bool) string {
c, err := repo.LastCommit()
if err != nil {
return ""
}
if human {
return humanize.Time(c.When)
}
return c.When.Format("01/02/2006 03:04:05 PM")
}
templ Index(ic IndexContext) {
@base(ic.BaseContext) {
<header>
<h1 class="text-text text-xl font-bold">{ ic.Title }</h1>
<h2 class="text-subtext1 text-lg">{ ic.Description }</h2>
</header>
<main class="mt-5">
<div class="grid grid-cols-1 sm:grid-cols-8">
if ic.Profile.Username != "" {
<div class="text-mauve">{ `@` + ic.Profile.Username }</div>
}
if ic.Profile.Email != "" {
<div class="text-mauve col-span-2">
<div class="w-5 h-5 stroke-mauve inline-block mr-1 align-middle">
@templ.Raw(string(assets.EmailIcon))
</div>
<a class="underline decoration-mauve/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL("mailto:" + ic.Profile.Email) }>{ ic.Profile.Email }</a>
</div>
}
</div>
<div class="grid grid-cols-1 sm:grid-cols-8">
for _, link := range ic.Profile.Links {
<div class="text-mauve">
<div class="w-5 h-5 stroke-mauve inline-block mr-1 align-middle">
@templ.Raw(string(assets.LinkIcon))
</div>
<a class="underline decoration-mauve/50 decoration-dashed hover:decoration-solid" rel="me" href={ templ.SafeURL(link.URL) }>{ link.Name }</a>
</div>
}
</div>
<div class="grid sm:grid-cols-8 gap-2 mt-5">
for _, repo := range ic.Repos {
<div class="sm:col-span-2 text-blue dark:text-lavender"><a class="underline decoration-blue/50 dark:decoration-lavender/50 decoration-dashed hover:decoration-solid" href={ templ.URL("/" + repo.Name()) }>{ repo.Name() }</a></div>
<div class="sm:col-span-4 text-subtext0">{ repo.Meta.Description }</div>
<div class="sm:col-span-1 text-subtext0">
for _, tag := range repo.Meta.Tags {
<a href={ templ.SafeURL("?tag=" + tag) } class="rounded border-rosewater border-solid border pb-0.5 px-1 mr-1 mb-1 inline-block">{ tag }</a>
}
</div>
<div class="sm:col-span-1 text-text/80 mb-4 sm:mb-0" title={ lastCommit(repo, false) }>{ lastCommit(repo, true) }</div>
}
</div>
</main>
}
}

View File

@ -1,310 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package html
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "go.jolheiser.com/ugit/internal/git"
import "github.com/dustin/go-humanize"
import "go.jolheiser.com/ugit/assets"
type IndexContext struct {
BaseContext
Profile IndexProfile
Repos []*git.Repo
}
type IndexProfile struct {
Username string
Email string
Links []IndexLink
}
type IndexLink struct {
Name string
URL string
}
func lastCommit(repo *git.Repo, human bool) string {
c, err := repo.LastCommit()
if err != nil {
return ""
}
if human {
return humanize.Time(c.When)
}
return c.When.Format("01/02/2006 03:04:05 PM")
}
func Index(ic IndexContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<header><h1 class=\"text-text text-xl font-bold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(ic.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 38, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1><h2 class=\"text-subtext1 text-lg\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(ic.Description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 39, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h2></header><main class=\"mt-5\"><div class=\"grid grid-cols-1 sm:grid-cols-8\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if ic.Profile.Username != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"text-mauve\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(`@` + ic.Profile.Username)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 44, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if ic.Profile.Email != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"text-mauve col-span-2\"><div class=\"w-5 h-5 stroke-mauve inline-block mr-1 align-middle\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(string(assets.EmailIcon)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><a class=\"underline decoration-mauve/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.SafeURL = templ.SafeURL("mailto:" + ic.Profile.Email)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(ic.Profile.Email)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 51, Col: 159}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"grid grid-cols-1 sm:grid-cols-8\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, link := range ic.Profile.Links {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"text-mauve\"><div class=\"w-5 h-5 stroke-mauve inline-block mr-1 align-middle\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(string(assets.LinkIcon)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><a class=\"underline decoration-mauve/50 decoration-dashed hover:decoration-solid\" rel=\"me\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 templ.SafeURL = templ.SafeURL(link.URL)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(link.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 61, Col: 141}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"grid sm:grid-cols-8 gap-2 mt-5\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, repo := range ic.Repos {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"sm:col-span-2 text-blue dark:text-lavender\"><a class=\"underline decoration-blue/50 dark:decoration-lavender/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 templ.SafeURL = templ.URL("/" + repo.Name())
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var10)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(repo.Name())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 67, Col: 221}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div class=\"sm:col-span-4 text-subtext0\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(repo.Meta.Description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 68, Col: 69}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"sm:col-span-1 text-subtext0\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, tag := range repo.Meta.Tags {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 templ.SafeURL = templ.SafeURL("?tag=" + tag)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var13)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"rounded border-rosewater border-solid border pb-0.5 px-1 mr-1 mb-1 inline-block\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(tag)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 71, Col: 141}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"sm:col-span-1 text-text/80 mb-4 sm:mb-0\" title=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(lastCommit(repo, false))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 74, Col: 89}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(lastCommit(repo, true))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 74, Col: 116}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = base(ic.BaseContext).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -2,6 +2,7 @@ package markup
import (
"io"
"path/filepath"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters/html"
@ -9,27 +10,27 @@ import (
"github.com/alecthomas/chroma/v2/styles"
)
var (
// Formatter is the default formatter
Formatter = html.New(
var customReg = map[string]string{
".hujson": "json",
}
// Options are the default set of formatting options
func Options(linePrefix string) []html.Option {
return []html.Option{
html.WithLineNumbers(true),
html.WithLinkableLineNumbers(true, "L"),
html.WithLinkableLineNumbers(true, linePrefix),
html.WithClasses(true),
html.LineNumbersInTable(true),
)
basicFormatter = html.New(
html.WithClasses(true),
)
// Code is the entrypoint for formatting
Code = code{}
)
type code struct{}
}
}
func setup(source []byte, fileName string) (chroma.Iterator, *chroma.Style, error) {
lexer := lexers.Match(fileName)
if lexer == nil {
lexer = lexers.Fallback
if name, ok := customReg[filepath.Ext(fileName)]; ok {
lexer = lexers.Get(name)
}
}
lexer = chroma.Coalesce(lexer)
@ -46,22 +47,13 @@ func setup(source []byte, fileName string) (chroma.Iterator, *chroma.Style, erro
return iter, style, nil
}
// Basic formats code without any extras
func (c code) Basic(source []byte, fileName string, writer io.Writer) error {
iter, style, err := setup(source, fileName)
if err != nil {
return err
}
return basicFormatter.Format(writer, style, iter)
}
// Convert formats code with line numbers, links, etc.
func (c code) Convert(source []byte, fileName string, writer io.Writer) error {
func Convert(source []byte, fileName, linePrefix string, writer io.Writer) error {
iter, style, err := setup(source, fileName)
if err != nil {
return err
}
return Formatter.Format(writer, style, iter)
return html.New(Options(linePrefix)...).Format(writer, style, iter)
}
// Snippet formats code with line numbers starting at a specific line

View File

@ -112,6 +112,7 @@ func (a astTransformer) Transform(node *ast.Document, _ text.Reader, pc parser.C
case *ast.Image:
link := v.Destination
if len(link) > 0 && !bytes.HasPrefix(link, []byte("http")) {
v.SetAttributeString("style", []byte("max-width:100%;"))
v.Destination = []byte(resolveLink(ctx.repo, ctx.ref, ctx.path, string(link)) + "?raw&pretty")
}

View File

@ -0,0 +1,17 @@
package html
import (
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
type ReadmeComponentContext struct {
Markdown string
}
func readmeComponent(rcc ReadmeComponentContext) Node {
if rcc.Markdown == "" {
return nil
}
return Div(Class("bg-base dark:bg-base/50 p-5 mt-5 rounded markdown"), Raw(rcc.Markdown))
}

View File

@ -1,12 +0,0 @@
package html
type ReadmeComponentContext struct {
Markdown string
}
templ readmeComponent(rcc ReadmeComponentContext) {
if rcc.Markdown != "" {
<div class="bg-base dark:bg-base/50 p-5 mt-5 rounded markdown">@templ.Raw(rcc.Markdown)</div>
}
}

View File

@ -1,49 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package html
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
type ReadmeComponentContext struct {
Markdown string
}
func readmeComponent(rcc ReadmeComponentContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if rcc.Markdown != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"bg-base dark:bg-base/50 p-5 mt-5 rounded markdown\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(rcc.Markdown).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,44 @@
package html
import (
"fmt"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
type RepoHeaderComponentContext struct {
Name string
Ref string
Description string
CloneURL string
Tags []string
}
func repoHeaderComponent(rhcc RepoHeaderComponentContext) Node {
return Group([]Node{
Div(Class("mb-1 text-text"),
A(Class("text-lg underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href("/"+rhcc.Name), Text(rhcc.Name)),
If(rhcc.Ref != "", Group([]Node{
Text(" "),
A(Class("text-text/80 text-sm underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/tree/%s/", rhcc.Name, rhcc.Ref)), Text("@"+rhcc.Ref)),
})),
Text(" - "),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/refs", rhcc.Name)), Text("refs")),
Text(" - "),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/log/%s", rhcc.Name, rhcc.Ref)), Text("log")),
Text(" - "),
Form(Class("inline-block"), Action(fmt.Sprintf("/%s/search", rhcc.Name)), Method("get"),
Input(Class("rounded p-1 bg-mantle focus:border-lavender focus:outline-none focus:ring-0"), ID("search"), Type("text"), Name("q"), Placeholder("search")),
),
Text(" - "),
Pre(Class("text-text inline select-all bg-base dark:bg-base/50 p-1 rounded"), Textf("%s/%s.git", rhcc.CloneURL, rhcc.Name)),
),
Div(Class("text-subtext0 mb-1"),
Map(rhcc.Tags, func(tag string) Node {
return Span(Class("rounded border-rosewater border-solid border pb-0.5 px-1 mr-1 mb-1 inline-block"), Text(tag))
}),
),
Div(Class("text-text/80 mb-1"), Text(rhcc.Description)),
})
}

View File

@ -1,35 +0,0 @@
package html
import "fmt"
type RepoHeaderComponentContext struct {
Name string
Ref string
Description string
CloneURL string
Tags []string
}
templ repoHeaderComponent(rhcc RepoHeaderComponentContext) {
<div class="mb-1 text-text">
<a class="text-lg underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL("/" + rhcc.Name) }>{ rhcc.Name }</a>
if rhcc.Ref != "" {
{ " " }
<a class="text-text/80 text-sm underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/", rhcc.Name, rhcc.Ref)) }>{ "@" + rhcc.Ref }</a>
}
{ " - " }
<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/refs", rhcc.Name)) }>refs</a>
{ " - " }
<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/log/%s", rhcc.Name, rhcc.Ref)) }>log</a>
{ " - " }
<form class="inline-block" action={ templ.SafeURL(fmt.Sprintf("/%s/search", rhcc.Name)) } method="get"><input class="rounded p-1 bg-mantle focus:border-lavender focus:outline-none focus:ring-0" id="search" type="text" name="q" placeholder="search"/></form>
{ " - " }
<pre class="text-text inline select-all bg-base dark:bg-base/50 p-1 rounded">{ fmt.Sprintf("%s/%s.git", rhcc.CloneURL, rhcc.Name) }</pre>
</div>
<div class="text-subtext0 mb-1">
for _, tag := range rhcc.Tags {
<span class="rounded border-rosewater border-solid border pb-0.5 px-1 mr-1 mb-1 inline-block">{ tag }</span>
}
</div>
<div class="text-text/80 mb-1">{ rhcc.Description }</div>
}

View File

@ -2,8 +2,11 @@ package html
import (
"fmt"
"strings"
"path"
"strings"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
type RepoBreadcrumbComponentContext struct {
@ -36,17 +39,19 @@ func (r RepoBreadcrumbComponentContext) crumbs() []breadcrumb {
return breadcrumbs
}
templ repoBreadcrumbComponent(rbcc RepoBreadcrumbComponentContext) {
if rbcc.Path != "" {
<div class="inline-block text-text">
for _, crumb := range rbcc.crumbs() {
func repoBreadcrumbComponent(rbcc RepoBreadcrumbComponentContext) Node {
if rbcc.Path == "" {
return nil
}
return Div(Class("inline-block text-text"),
Map(rbcc.crumbs(), func(crumb breadcrumb) Node {
if crumb.end {
<span>{ crumb.label }</span>
} else {
<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(crumb.href) }>{ crumb.label }</a>
{ " / " }
}
}
</div>
return Span(Text(crumb.label))
}
return Group([]Node{
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(crumb.href), Text(crumb.label)),
Text(" / "),
})
}),
)
}

View File

@ -1,134 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package html
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"fmt"
"path"
"strings"
)
type RepoBreadcrumbComponentContext struct {
Repo string
Ref string
Path string
}
type breadcrumb struct {
label string
href string
end bool
}
func (r RepoBreadcrumbComponentContext) crumbs() []breadcrumb {
parts := strings.Split(r.Path, "/")
breadcrumbs := []breadcrumb{
{
label: r.Repo,
href: fmt.Sprintf("/%s/tree/%s/", r.Repo, r.Ref),
},
}
for idx, part := range parts {
breadcrumbs = append(breadcrumbs, breadcrumb{
label: part,
href: path.Join(breadcrumbs[idx].href, part),
})
}
breadcrumbs[len(breadcrumbs)-1].end = true
return breadcrumbs
}
func repoBreadcrumbComponent(rbcc RepoBreadcrumbComponentContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if rbcc.Path != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"inline-block text-text\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, crumb := range rbcc.crumbs() {
if crumb.end {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(crumb.label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_breadcrumb.templ`, Line: 44, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL(crumb.href)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(crumb.label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_breadcrumb.templ`, Line: 46, Col: 134}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(" / ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_breadcrumb.templ`, Line: 47, Col: 12}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,72 @@
package html
import (
"fmt"
"github.com/dustin/go-humanize"
"go.jolheiser.com/ugit/internal/git"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
type RepoCommitContext struct {
BaseContext
RepoHeaderComponentContext
Commit git.Commit
}
func RepoCommitTemplate(rcc RepoCommitContext) Node {
return base(rcc.BaseContext, []Node{
repoHeaderComponent(rcc.RepoHeaderComponentContext),
Div(Class("text-text mt-5"),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/tree/%s/", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA)), Text("tree")),
Text(" "),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/log/%s", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA)), Text("log")),
Text(" "),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/commit/%s.patch", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA)), Text("patch")),
),
Div(Class("text-text whitespace-pre mt-5 p-3 bg-base rounded"), Text(rcc.Commit.Message)),
If(rcc.Commit.Signature != "",
Details(Class("text-text whitespace-pre"),
Summary(Class("cursor-pointer"), Text("Signature")),
Div(Class("p-3 bg-base rounded"),
Code(Text(rcc.Commit.Signature)),
),
),
),
Div(Class("text-text mt-3"),
Div(
Text(rcc.Commit.Author),
Text(" "),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("mailto:%s", rcc.Commit.Email)), Text(fmt.Sprintf("<%s>", rcc.Commit.Email))),
),
Div(Title(rcc.Commit.When.Format("01/02/2006 03:04:05 PM")), Text(humanize.Time(rcc.Commit.When))),
),
Details(Class("text-text mt-5"),
Summary(Class("cursor-pointer"), Textf("%d changed files, %d additions(+), %d deletions(-)", rcc.Commit.Stats.Changed, rcc.Commit.Stats.Additions, rcc.Commit.Stats.Deletions)),
Div(Class("p-3 bg-base rounded"),
Map(rcc.Commit.Files, func(file git.CommitFile) Node {
return A(Class("block underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href("#"+file.Path()), Text(file.Path()))
}),
),
),
Map(rcc.Commit.Files, func(file git.CommitFile) Node {
return Group([]Node{
Div(Class("text-text mt-5"), ID(file.Path()),
Span(Class("text-text/80"), Title(file.Action), Text(string(file.Action[0]))),
Text(" "),
If(file.From.Path != "",
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/tree/%s/%s", rcc.RepoHeaderComponentContext.Name, file.From.Commit, file.From.Path)), Text(file.From.Path)),
),
If(file.From.Path != "" && file.To.Path != "", Text(" → ")),
If(file.To.Path != "",
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/tree/%s/%s", rcc.RepoHeaderComponentContext.Name, file.To.Commit, file.To.Path)), Text(file.To.Path)),
),
),
Div(Class("code"),
Raw(file.Patch),
),
})
}),
}...)
}

View File

@ -1,47 +0,0 @@
package html
import "fmt"
import "github.com/dustin/go-humanize"
import "go.jolheiser.com/ugit/internal/git"
type RepoCommitContext struct{
BaseContext
RepoHeaderComponentContext
Commit git.Commit
}
templ RepoCommit(rcc RepoCommitContext) {
@base(rcc.BaseContext) {
@repoHeaderComponent(rcc.RepoHeaderComponentContext)
<div class="text-text mt-5"><a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA)) }>tree</a>{ " " }<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/log/%s", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA)) }>log</a>{ " " }<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/commit/%s.patch", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA)) }>patch</a></div>
<div class="text-text whitespace-pre mt-5 p-3 bg-base rounded">{ rcc.Commit.Message }</div>
if rcc.Commit.Signature != "" {
<details class="text-text whitespace-pre">
<summary class="cursor-pointer">Signature</summary>
<div class="p-3 bg-base rounded"><code>{ rcc.Commit.Signature }</code></div>
</details>
}
<div class="text-text mt-3">
<div>{ rcc.Commit.Author }{ " " }<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("mailto:%s", rcc.Commit.Email)) }>{ fmt.Sprintf("<%s>", rcc.Commit.Email) }</a></div>
<div title={ rcc.Commit.When.Format("01/02/2006 03:04:05 PM") }>{ humanize.Time(rcc.Commit.When) }</div>
</div>
<div class="text-text mt-5">{ fmt.Sprintf("%d changed files, %d additions(+), %d deletions(-)", rcc.Commit.Stats.Changed, rcc.Commit.Stats.Additions, rcc.Commit.Stats.Deletions) }</div>
for _, file := range rcc.Commit.Files {
<div class="text-text mt-5">
<span class="text-text/80" title={ file.Action }>{ string(file.Action[0]) }</span>
{ " " }
if file.From.Path != "" {
<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rcc.RepoHeaderComponentContext.Name, file.From.Commit, file.From.Path)) }>{ file.From.Path }</a>
}
if file.From.Path != "" && file.To.Path != "" {
{ " -> " }
}
if file.To.Path != "" {
<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rcc.RepoHeaderComponentContext.Name, file.To.Commit, file.To.Path)) }>{ file.To.Path }</a>
}
</div>
<div class="whitespace-pre code">@templ.Raw(file.Patch)</div>
}
}
}

View File

@ -1,364 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package html
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "fmt"
import "github.com/dustin/go-humanize"
import "go.jolheiser.com/ugit/internal/git"
type RepoCommitContext struct {
BaseContext
RepoHeaderComponentContext
Commit git.Commit
}
func RepoCommit(rcc RepoCommitContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
templ_7745c5c3_Err = repoHeaderComponent(rcc.RepoHeaderComponentContext).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div class=\"text-text mt-5\"><a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">tree</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 16, Col: 229}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/log/%s", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">log</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 16, Col: 427}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/commit/%s.patch", rcc.RepoHeaderComponentContext.Name, rcc.Commit.SHA))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">patch</a></div><div class=\"text-text whitespace-pre mt-5 p-3 bg-base rounded\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(rcc.Commit.Message)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 17, Col: 85}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if rcc.Commit.Signature != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<details class=\"text-text whitespace-pre\"><summary class=\"cursor-pointer\">Signature</summary><div class=\"p-3 bg-base rounded\"><code>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(rcc.Commit.Signature)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 21, Col: 65}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</code></div></details>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div class=\"text-text mt-3\"><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(rcc.Commit.Author)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 25, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 25, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 templ.SafeURL = templ.SafeURL(fmt.Sprintf("mailto:%s", rcc.Commit.Email))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("<%s>", rcc.Commit.Email))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 25, Col: 223}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div title=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(rcc.Commit.When.Format("01/02/2006 03:04:05 PM"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 26, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(humanize.Time(rcc.Commit.When))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 26, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"text-text mt-5\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d changed files, %d additions(+), %d deletions(-)", rcc.Commit.Stats.Changed, rcc.Commit.Stats.Additions, rcc.Commit.Stats.Deletions))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 28, Col: 179}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, file := range rcc.Commit.Files {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"text-text mt-5\"><span class=\"text-text/80\" title=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(file.Action)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 31, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(string(file.Action[0]))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 31, Col: 77}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 32, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if file.From.Path != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rcc.RepoHeaderComponentContext.Name, file.From.Commit, file.From.Path))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var20)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(file.From.Path)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 34, Col: 227}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if file.From.Path != "" && file.To.Path != "" {
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(" -> ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 37, Col: 13}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if file.To.Path != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rcc.RepoHeaderComponentContext.Name, file.To.Commit, file.To.Path))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var23)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(file.To.Path)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 40, Col: 221}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"whitespace-pre code\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(file.Patch).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = base(rcc.BaseContext).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,40 @@
package html
import (
_ "embed"
"fmt"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
type RepoFileContext struct {
BaseContext
RepoHeaderComponentContext
RepoBreadcrumbComponentContext
Code string
Commit string
Path string
}
//go:embed repo_file.js
var repoFileJS string
func RepoFileTemplate(rfc RepoFileContext) Node {
permalink := fmt.Sprintf("/%s/tree/%s/%s", rfc.RepoBreadcrumbComponentContext.Repo, rfc.Commit, rfc.Path)
return base(rfc.BaseContext, []Node{
repoHeaderComponent(rfc.RepoHeaderComponentContext),
Div(Class("mt-2 text-text"),
repoBreadcrumbComponent(rfc.RepoBreadcrumbComponentContext),
Text(" - "),
A(Class("text-text underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href("?raw"), Text("raw")),
Text(" - "),
A(Class("text-text underline decoration-text/50 decoration-dashed hover:decoration-solid"), ID("permalink"), Data("permalink", permalink), Href(permalink), Text("permalink")),
Div(Class("code relative"),
Raw(rfc.Code),
Button(ID("copy"), Class("absolute top-0 right-0 rounded bg-base hover:bg-surface0")),
),
),
Script(Raw(repoFileJS)),
}...)
}

View File

@ -0,0 +1,70 @@
const lineRe = /#L(\d+)(?:-L(\d+))?/g
const $lineLines = document.querySelectorAll(".chroma .lntable .lnt");
const $codeLines = document.querySelectorAll(".chroma .lntable .line");
const $copyButton = document.getElementById('copy');
const $permalink = document.getElementById('permalink');
const $copyIcon = "📋";
const $copiedIcon = "✅";
let $code = ""
for (let codeLine of $codeLines) $code += codeLine.innerText;
let start = 0;
let end = 0;
const results = [...location.hash.matchAll(lineRe)];
if (0 in results) {
start = results[0][1] !== undefined ? parseInt(results[0][1]) : 0;
end = results[0][2] !== undefined ? parseInt(results[0][2]) : 0;
}
if (start !== 0) {
deactivateLines();
activateLines(start, end);
let anchor = `#${start}`;
if (end !== 0) anchor += `-${end}`;
if (anchor !== "") $permalink.href = $permalink.dataset.permalink + anchor;
$lineLines[start - 1].scrollIntoView(true);
}
for (let line of $lineLines) {
line.addEventListener("click", (event) => {
event.preventDefault();
deactivateLines();
const n = parseInt(line.id.substring(1));
let anchor = "";
if (event.shiftKey) {
end = n;
anchor = `#L${start}-L${end}`;
} else if (start === n) {
start = 0;
end = 0;
} else {
start = n;
end = 0;
anchor = `#L${start}`;
}
history.replaceState(null, null, window.location.pathname + anchor);
$permalink.href = $permalink.dataset.permalink + anchor;
if (start !== 0) activateLines(start, end);
});
}
if (navigator.clipboard && navigator.clipboard.writeText) {
$copyButton.innerText = $copyIcon;
$copyButton.classList.remove("hidden");
}
$copyButton.addEventListener("click", () => {
navigator.clipboard.writeText($code);
$copyButton.innerText = $copiedIcon;
setTimeout(() => {
$copyButton.innerText = $copyIcon;
}, 1000);
});
function activateLines(start, end) {
if (end < start) end = start;
for (let idx = start - 1; idx < end; idx++) {
$codeLines[idx].classList.add("active");
}
}
function deactivateLines() {
for (let code of $codeLines) {
code.classList.remove("active");
}
}

View File

@ -1,74 +0,0 @@
package html
type RepoFileContext struct {
BaseContext
RepoHeaderComponentContext
RepoBreadcrumbComponentContext
Code string
}
templ RepoFile(rfc RepoFileContext) {
@base(rfc.BaseContext) {
@repoHeaderComponent(rfc.RepoHeaderComponentContext)
<div class="mt-2 text-text">
@repoBreadcrumbComponent(rfc.RepoBreadcrumbComponentContext)
{ " - " }
<a class="text-text underline decoration-text/50 decoration-dashed hover:decoration-solid" href="?raw">raw</a>
<div class="code">
@templ.Raw(rfc.Code)
</div>
</div>
}
<script>
const lineRe = /#L(\d+)(?:-L(\d+))?/g
const $lineLines = document.querySelectorAll(".chroma .lntable .lnt");
const $codeLines = document.querySelectorAll(".chroma .lntable .line");
let start = 0;
let end = 0;
const results = [...location.hash.matchAll(lineRe)];
if (0 in results) {
start = results[0][1] !== undefined ? parseInt(results[0][1]) : 0;
end = results[0][2] !== undefined ? parseInt(results[0][2]) : 0;
}
if (start != 0) {
deactivateLines();
activateLines(start, end);
}
for (let line of $lineLines) {
line.addEventListener("click", (event) => {
event.preventDefault();
deactivateLines();
const n = parseInt(line.id.substring(1));
let anchor = "";
if (event.shiftKey) {
end = n;
anchor = `#L${start}-L${end}`;
} else {
start = n;
end = 0;
anchor = `#L${start}`;
}
history.replaceState(null, null, anchor);
activateLines(start, end);
});
}
function activateLines(start, end) {
if (end < start) end = start;
for (let idx = start - 1; idx < end; idx++) {
$codeLines[idx].classList.add("active");
}
}
function deactivateLines() {
for (let code of $codeLines) {
code.classList.remove("active");
}
}
</script>
}

View File

@ -1,90 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package html
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
type RepoFileContext struct {
BaseContext
RepoHeaderComponentContext
RepoBreadcrumbComponentContext
Code string
}
func RepoFile(rfc RepoFileContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
templ_7745c5c3_Err = repoHeaderComponent(rfc.RepoHeaderComponentContext).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div class=\"mt-2 text-text\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = repoBreadcrumbComponent(rfc.RepoBreadcrumbComponentContext).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(" - ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_file.templ`, Line: 15, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <a class=\"text-text underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"?raw\">raw</a><div class=\"code\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(rfc.Code).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = base(rfc.BaseContext).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script>\n\t\tconst lineRe = /#L(\\d+)(?:-L(\\d+))?/g\n\t\tconst $lineLines = document.querySelectorAll(\".chroma .lntable .lnt\");\n\t\tconst $codeLines = document.querySelectorAll(\".chroma .lntable .line\");\n\t\tlet start = 0;\n\t\tlet end = 0;\n\n\t\tconst results = [...location.hash.matchAll(lineRe)];\t\t\n\t\tif (0 in results) {\n\t\t\tstart = results[0][1] !== undefined ? parseInt(results[0][1]) : 0;\n\t\t\tend = results[0][2] !== undefined ? parseInt(results[0][2]) : 0;\n\t\t}\n\t\tif (start != 0) {\n\t\t\tdeactivateLines();\n\t\t\tactivateLines(start, end);\n\t\t}\n\n\t\tfor (let line of $lineLines) {\n\t\t\tline.addEventListener(\"click\", (event) => {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tdeactivateLines();\n\t\t\t\tconst n = parseInt(line.id.substring(1));\n\t\t\t\tlet anchor = \"\";\n\t\t\t\tif (event.shiftKey) {\n\t\t\t\t\tend = n;\n\t\t\t\t\tanchor = `#L${start}-L${end}`;\n\t\t\t\t} else {\n\t\t\t\t\tstart = n;\n\t\t\t\t\tend = 0;\n\t\t\t\t\tanchor = `#L${start}`;\n\t\t\t\t}\n\t\t\t\thistory.replaceState(null, null, anchor);\n\t\t\t\tactivateLines(start, end);\n\t\t\t});\n\t\t}\n\n\t\tfunction activateLines(start, end) {\n\t\t\tif (end < start) end = start;\n\t\t\tfor (let idx = start - 1; idx < end; idx++) {\n\t\t\t\t$codeLines[idx].classList.add(\"active\");\n\t\t\t}\n\t\t}\n\n\t\tfunction deactivateLines() {\n\t\t\tfor (let code of $codeLines) {\n\t\t\t\tcode.classList.remove(\"active\");\n\t\t\t}\n\t\t}\n\n\t\t\n\t\t\n\t</script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,52 @@
package html
import (
"fmt"
"github.com/dustin/go-humanize"
"go.jolheiser.com/ugit/internal/git"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
type RepoLogContext struct {
BaseContext
RepoHeaderComponentContext
Commits []git.Commit
}
func RepoLogTemplate(rlc RepoLogContext) Node {
return base(rlc.BaseContext, []Node{
repoHeaderComponent(rlc.RepoHeaderComponentContext),
Div(Class("grid sm:grid-cols-8 gap-1 text-text mt-5"),
Map(rlc.Commits, func(commit git.Commit) Node {
return Group([]Node{
Div(Class("sm:col-span-5"),
Div(
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/commit/%s", rlc.RepoHeaderComponentContext.Name, commit.SHA)), Text(commit.Short())),
),
Div(Class("whitespace-pre"),
If(commit.Details() != "",
Details(
Summary(Class("cursor-pointer"), Text(commit.Summary())),
Div(Class("p-3 bg-base rounded"), Text(commit.Details())),
),
),
If(commit.Details() == "",
Text(commit.Message),
),
),
),
Div(Class("sm:col-span-3 mb-4"),
Div(
Text(commit.Author),
Text(" "),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("mailto:%s", commit.Email)), Textf("<%s>", commit.Email)),
),
Div(Title(commit.When.Format("01/02/2006 03:04:05 PM")), Text(humanize.Time(commit.When))),
),
})
}),
),
}...)
}

View File

@ -1,39 +0,0 @@
package html
import "fmt"
import "github.com/dustin/go-humanize"
import "go.jolheiser.com/ugit/internal/git"
type RepoLogContext struct {
BaseContext
RepoHeaderComponentContext
Commits []git.Commit
}
templ RepoLog(rlc RepoLogContext) {
@base(rlc.BaseContext) {
@repoHeaderComponent(rlc.RepoHeaderComponentContext)
<div class="grid sm:grid-cols-8 gap-1 text-text mt-5">
for _, commit := range rlc.Commits {
<div class="sm:col-span-5">
<div><a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/commit/%s", rlc.RepoHeaderComponentContext.Name, commit.SHA)) }>{ commit.Short() }</a></div>
<div class="whitespace-pre">
if commit.Details() != "" {
<details>
<summary class="cursor-pointer">{ commit.Summary() }</summary>
<div class="p-3 bg-base rounded">{ commit.Details() }</div>
</details>
} else {
{ commit.Message }
}
</div>
</div>
<div class="sm:col-span-3 mb-4">
<div>{ commit.Author }{ " " }<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("mailto:%s", commit.Email)) }>{ fmt.Sprintf("<%s>", commit.Email) }</a></div>
<div title={ commit.When.Format("01/02/2006 03:04:05 PM") }>{ humanize.Time(commit.When) }</div>
</div>
}
</div>
}
}

View File

@ -1,212 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package html
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "fmt"
import "github.com/dustin/go-humanize"
import "go.jolheiser.com/ugit/internal/git"
type RepoLogContext struct {
BaseContext
RepoHeaderComponentContext
Commits []git.Commit
}
func RepoLog(rlc RepoLogContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
templ_7745c5c3_Err = repoHeaderComponent(rlc.RepoHeaderComponentContext).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div class=\"grid sm:grid-cols-8 gap-1 text-text mt-5\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, commit := range rlc.Commits {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"sm:col-span-5\"><div><a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/commit/%s", rlc.RepoHeaderComponentContext.Name, commit.SHA))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Short())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 19, Col: 209}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div class=\"whitespace-pre\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if commit.Details() != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<details><summary class=\"cursor-pointer\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Summary())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 23, Col: 58}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</summary><div class=\"p-3 bg-base rounded\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Details())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 24, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></details>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Message)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 27, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"sm:col-span-3 mb-4\"><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(commit.Author)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 32, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 32, Col: 32}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 templ.SafeURL = templ.SafeURL(fmt.Sprintf("mailto:%s", commit.Email))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var10)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("<%s>", commit.Email))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 32, Col: 213}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div title=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(commit.When.Format("01/02/2006 03:04:05 PM"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 33, Col: 62}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(humanize.Time(commit.When))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_log.templ`, Line: 33, Col: 93}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = base(rlc.BaseContext).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,61 @@
package html
import (
"fmt"
"go.jolheiser.com/ugit/internal/git"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
type RepoRefsContext struct {
BaseContext
RepoHeaderComponentContext
Branches []string
Tags []git.Tag
}
func RepoRefsTemplate(rrc RepoRefsContext) Node {
return base(rrc.BaseContext, []Node{
repoHeaderComponent(rrc.RepoHeaderComponentContext),
If(len(rrc.Branches) > 0, Group([]Node{
H3(Class("text-text text-lg mt-5"), Text("Branches")),
Div(Class("text-text grid grid-cols-4 sm:grid-cols-8"),
Map(rrc.Branches, func(branch string) Node {
return Group([]Node{
Div(Class("col-span-2 sm:col-span-1 font-bold"), Text(branch)),
Div(Class("col-span-2 sm:col-span-7"),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/tree/%s/", rrc.RepoHeaderComponentContext.Name, branch)), Text("tree")),
Text(" "),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/log/%s", rrc.RepoHeaderComponentContext.Name, branch)), Text("log")),
),
})
}),
),
})),
If(len(rrc.Tags) > 0, Group([]Node{
H3(Class("text-text text-lg mt-5"), Text("Tags")),
Div(Class("text-text grid grid-cols-8"),
Map(rrc.Tags, func(tag git.Tag) Node {
return Group([]Node{
Div(Class("col-span-1 font-bold"), Text(tag.Name)),
Div(Class("col-span-7"),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/tree/%s/", rrc.RepoHeaderComponentContext.Name, tag.Name)), Text("tree")),
Text(" "),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/log/%s", rrc.RepoHeaderComponentContext.Name, tag.Name)), Text("log")),
),
If(tag.Signature != "",
Details(Class("col-span-8 whitespace-pre"),
Summary(Class("cursor-pointer"), Text("Signature")),
Code(Text(tag.Signature)),
),
),
If(tag.Annotation != "",
Div(Class("col-span-8 mb-3"), Text(tag.Annotation)),
),
})
}),
),
})),
}...)
}

View File

@ -1,42 +0,0 @@
package html
import "fmt"
import "go.jolheiser.com/ugit/internal/git"
type RepoRefsContext struct {
BaseContext
RepoHeaderComponentContext
Branches []string
Tags []git.Tag
}
templ RepoRefs(rrc RepoRefsContext) {
@base(rrc.BaseContext) {
@repoHeaderComponent(rrc.RepoHeaderComponentContext)
if len(rrc.Branches) > 0 {
<h3 class="text-text text-lg mt-5">Branches</h3>
<div class="text-text grid grid-cols-4 sm:grid-cols-8">
for _, branch := range rrc.Branches {
<div class="col-span-2 sm:col-span-1 font-bold">{ branch }</div>
<div class="col-span-2 sm:col-span-7"><a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/", rrc.RepoHeaderComponentContext.Name, branch)) }>tree</a>{ " " }<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/log/%s", rrc.RepoHeaderComponentContext.Name, branch)) }>log</a></div>
}
</div>
}
if len(rrc.Tags) > 0 {
<h3 class="text-text text-lg mt-5">Tags</h3>
<div class="text-text grid grid-cols-8">
for _, tag := range rrc.Tags {
<div class="col-span-1 font-bold">{ tag.Name }</div>
<div class="col-span-7"><a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/", rrc.RepoHeaderComponentContext.Name, tag.Name)) }>tree</a>{ " " }<a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/log/%s", rrc.RepoHeaderComponentContext.Name, tag.Name)) }>log</a></div>
if tag.Signature != "" {
<details class="col-span-8 whitespace-pre"><summary class="cursor-pointer">Signature</summary><code>{ tag.Signature }</code></details>
}
if tag.Annotation != "" {
<div class="col-span-8 mb-3">{ tag.Annotation }</div>
}
}
</div>
}
}
}

View File

@ -1,230 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package html
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "fmt"
import "go.jolheiser.com/ugit/internal/git"
type RepoRefsContext struct {
BaseContext
RepoHeaderComponentContext
Branches []string
Tags []git.Tag
}
func RepoRefs(rrc RepoRefsContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
templ_7745c5c3_Err = repoHeaderComponent(rrc.RepoHeaderComponentContext).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(rrc.Branches) > 0 {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h3 class=\"text-text text-lg mt-5\">Branches</h3><div class=\"text-text grid grid-cols-4 sm:grid-cols-8\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, branch := range rrc.Branches {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"col-span-2 sm:col-span-1 font-bold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(branch)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_refs.templ`, Line: 20, Col: 61}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"col-span-2 sm:col-span-7\"><a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/", rrc.RepoHeaderComponentContext.Name, branch))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">tree</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_refs.templ`, Line: 21, Col: 234}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/log/%s", rrc.RepoHeaderComponentContext.Name, branch))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">log</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(rrc.Tags) > 0 {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h3 class=\"text-text text-lg mt-5\">Tags</h3><div class=\"text-text grid grid-cols-8\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, tag := range rrc.Tags {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"col-span-1 font-bold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(tag.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_refs.templ`, Line: 29, Col: 49}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"col-span-7\"><a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/", rrc.RepoHeaderComponentContext.Name, tag.Name))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">tree</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_refs.templ`, Line: 30, Col: 222}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/log/%s", rrc.RepoHeaderComponentContext.Name, tag.Name))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var10)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">log</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if tag.Signature != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<details class=\"col-span-8 whitespace-pre\"><summary class=\"cursor-pointer\">Signature</summary><code>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(tag.Signature)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_refs.templ`, Line: 32, Col: 121}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</code></details>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if tag.Annotation != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"col-span-8 mb-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(tag.Annotation)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_refs.templ`, Line: 35, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = base(rrc.BaseContext).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,78 @@
package html
import (
_ "embed"
"fmt"
"go.jolheiser.com/ugit/internal/git"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
type SearchContext struct {
BaseContext
RepoHeaderComponentContext
Results []git.GrepResult
}
func (s SearchContext) DedupeResults() [][]git.GrepResult {
var (
results [][]git.GrepResult
currentFile string
)
var idx int
for _, result := range s.Results {
if result.File == currentFile {
results[idx-1] = append(results[idx-1], result)
continue
}
results = append(results, []git.GrepResult{result})
currentFile = result.File
idx++
}
return results
}
//go:embed repo_search.js
var repoSearchJS string
func repoSearchResult(repo, ref string, results []git.GrepResult) Node {
return Group([]Node{
Div(Class("text-text mt-5"),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/tree/%s/%s#L%d", repo, ref, results[0].File, results[0].Line)), Text(results[0].File)),
),
Div(Class("code"),
Raw(results[0].Content),
),
If(len(results) > 1,
Details(Class("text-text cursor-pointer"),
Summary(Textf("%d more", len(results[1:]))),
Map(results[1:], func(result git.GrepResult) Node {
return Group([]Node{
Div(Class("text-text mt-5 ml-5"),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/tree/%s/%s#L%d", repo, ref, result.File, result.Line)), Text(results[0].File)),
),
Div(Class("code ml-5"),
Raw(result.Content),
),
})
}),
),
),
})
}
func RepoSearchTemplate(sc SearchContext) Node {
dedupeResults := sc.DedupeResults()
return base(sc.BaseContext, []Node{
repoHeaderComponent(sc.RepoHeaderComponentContext),
Map(dedupeResults, func(results []git.GrepResult) Node {
return repoSearchResult(sc.RepoHeaderComponentContext.Name, sc.RepoHeaderComponentContext.Ref, results)
}),
If(len(dedupeResults) == 0,
P(Class("text-text mt-5 text-lg"), Text("No results")),
),
Script(Raw(repoSearchJS)),
}...)
}

View File

@ -0,0 +1,2 @@
const search = new URLSearchParams(window.location.search).get("q");
if (search !== "") document.querySelector("#search").value = search;

View File

@ -1,65 +0,0 @@
package html
import "fmt"
import "go.jolheiser.com/ugit/internal/git"
type SearchContext struct {
BaseContext
RepoHeaderComponentContext
Results []git.GrepResult
}
func (s SearchContext) DedupeResults() [][]git.GrepResult {
var (
results [][]git.GrepResult
currentFile string
currentResults []git.GrepResult
)
for _, result := range s.Results {
if result.File == currentFile {
currentResults = append(currentResults, result)
continue
}
if currentFile != "" {
results = append(results, currentResults)
}
currentFile = result.File
currentResults = []git.GrepResult{result}
}
return results
}
templ RepoSearch(sc SearchContext) {
@base(sc.BaseContext) {
@repoHeaderComponent(sc.RepoHeaderComponentContext)
for _, results := range sc.DedupeResults() {
@repoSearchResult(sc.RepoHeaderComponentContext.Name, sc.RepoHeaderComponentContext.Ref, results)
}
if len(sc.DedupeResults()) == 0 {
<p class="text-text mt-5 text-lg">No results</p>
}
}
<script>
const search = new URLSearchParams(window.location.search).get("q");
if (search !== "") document.querySelector("#search").value = search;
</script>
}
templ repoSearchResult(repo, ref string, results []git.GrepResult) {
<div class="text-text mt-5"><a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s#L%d", repo, ref, results[0].File, results[0].Line)) }>{ results[0].File }</a></div>
<div class="code">
@templ.Raw(results[0].Content)
</div>
if len(results) > 1 {
<details class="text-text cursor-pointer">
<summary>{ fmt.Sprintf("%d ", len(results[1:])) }more</summary>
for _, result := range results[1:] {
<div class="text-text mt-5 ml-5"><a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s#L%d", repo, ref, result.File, result.Line)) }>{ results[0].File }</a></div>
<div class="code ml-5">
@templ.Raw(result.Content)
</div>
}
</details>
}
}

View File

@ -1,213 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package html
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "fmt"
import "go.jolheiser.com/ugit/internal/git"
type SearchContext struct {
BaseContext
RepoHeaderComponentContext
Results []git.GrepResult
}
func (s SearchContext) DedupeResults() [][]git.GrepResult {
var (
results [][]git.GrepResult
currentFile string
currentResults []git.GrepResult
)
for _, result := range s.Results {
if result.File == currentFile {
currentResults = append(currentResults, result)
continue
}
if currentFile != "" {
results = append(results, currentResults)
}
currentFile = result.File
currentResults = []git.GrepResult{result}
}
return results
}
func RepoSearch(sc SearchContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
templ_7745c5c3_Err = repoHeaderComponent(sc.RepoHeaderComponentContext).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, results := range sc.DedupeResults() {
templ_7745c5c3_Err = repoSearchResult(sc.RepoHeaderComponentContext.Name, sc.RepoHeaderComponentContext.Ref, results).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(sc.DedupeResults()) == 0 {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p class=\"text-text mt-5 text-lg\">No results</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = base(sc.BaseContext).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script>\n\t\tconst search = new URLSearchParams(window.location.search).get(\"q\");\n\t\tif (search !== \"\") document.querySelector(\"#search\").value = search;\n\t</script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func repoSearchResult(repo, ref string, results []git.GrepResult) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"text-text mt-5\"><a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s#L%d", repo, ref, results[0].File, results[0].Line))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(results[0].File)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_search.templ`, Line: 50, Col: 230}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div class=\"code\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(results[0].Content).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(results) > 1 {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<details class=\"text-text cursor-pointer\"><summary>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d ", len(results[1:])))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_search.templ`, Line: 56, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("more</summary> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, result := range results[1:] {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"text-text mt-5 ml-5\"><a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s#L%d", repo, ref, result.File, result.Line))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(results[0].File)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_search.templ`, Line: 58, Col: 230}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div class=\"code ml-5\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(result.Content).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</details>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -1,232 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package html
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "fmt"
type RepoHeaderComponentContext struct {
Name string
Ref string
Description string
CloneURL string
Tags []string
}
func repoHeaderComponent(rhcc RepoHeaderComponentContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"mb-1 text-text\"><a class=\"text-lg underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.SafeURL("/" + rhcc.Name)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(rhcc.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo.templ`, Line: 15, Col: 142}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if rhcc.Ref != "" {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo.templ`, Line: 17, Col: 8}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <a class=\"text-text/80 text-sm underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/", rhcc.Name, rhcc.Ref))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("@" + rhcc.Ref)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo.templ`, Line: 18, Col: 194}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(" - ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo.templ`, Line: 20, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/refs", rhcc.Name))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">refs</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(" - ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo.templ`, Line: 22, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/log/%s", rhcc.Name, rhcc.Ref))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var10)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">log</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(" - ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo.templ`, Line: 24, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form class=\"inline-block\" action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/search", rhcc.Name))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" method=\"get\"><input class=\"rounded p-1 bg-mantle focus:border-lavender focus:outline-none focus:ring-0\" id=\"search\" type=\"text\" name=\"q\" placeholder=\"search\"></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(" - ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo.templ`, Line: 26, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<pre class=\"text-text inline select-all bg-base dark:bg-base/50 p-1 rounded\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s/%s.git", rhcc.CloneURL, rhcc.Name))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo.templ`, Line: 27, Col: 131}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</pre></div><div class=\"text-subtext0 mb-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, tag := range rhcc.Tags {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"rounded border-rosewater border-solid border pb-0.5 px-1 mr-1 mb-1 inline-block\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(tag)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo.templ`, Line: 31, Col: 102}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"text-text/80 mb-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(rhcc.Description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo.templ`, Line: 34, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,61 @@
package html
import (
"fmt"
"go.jolheiser.com/ugit/internal/git"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
type RepoTreeContext struct {
BaseContext
RepoHeaderComponentContext
RepoBreadcrumbComponentContext
RepoTreeComponentContext
ReadmeComponentContext
Description string
}
type RepoTreeComponentContext struct {
Repo string
Ref string
Tree []git.FileInfo
Back string
}
func slashDir(name string, isDir bool) string {
if isDir {
return name + "/"
}
return name
}
func repoTreeComponent(rtcc RepoTreeComponentContext) Node {
return Div(Class("grid grid-cols-3 sm:grid-cols-8 text-text py-5 rounded px-5 gap-x-3 gap-y-1 bg-base dark:bg-base/50"),
If(rtcc.Back != "", Group([]Node{
Div(Class("col-span-2")),
Div(Class("sm:col-span-6"),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/tree/%s/%s", rtcc.Repo, rtcc.Ref, rtcc.Back)), Text("..")),
),
})),
Map(rtcc.Tree, func(fi git.FileInfo) Node {
return Group([]Node{
Div(Class("sm:col-span-1 break-keep"), Text(fi.Mode)),
Div(Class("sm:col-span-1 text-right"), Text(fi.Size)),
Div(Class("sm:col-span-6 overflow-hidden text-ellipsis"),
A(Class("underline decoration-text/50 decoration-dashed hover:decoration-solid"), Href(fmt.Sprintf("/%s/tree/%s/%s", rtcc.Repo, rtcc.Ref, fi.Path)), Text(slashDir(fi.Name(), fi.IsDir))),
),
})
}),
)
}
func RepoTreeTemplate(rtc RepoTreeContext) Node {
return base(rtc.BaseContext, []Node{
repoHeaderComponent(rtc.RepoHeaderComponentContext),
repoBreadcrumbComponent(rtc.RepoBreadcrumbComponentContext),
repoTreeComponent(rtc.RepoTreeComponentContext),
readmeComponent(rtc.ReadmeComponentContext),
}...)
}

View File

@ -1,52 +0,0 @@
package html
import (
"fmt"
"go.jolheiser.com/ugit/internal/git"
)
type RepoTreeContext struct {
BaseContext
RepoHeaderComponentContext
RepoBreadcrumbComponentContext
RepoTreeComponentContext
ReadmeComponentContext
Description string
}
templ RepoTree(rtc RepoTreeContext) {
@base(rtc.BaseContext) {
@repoHeaderComponent(rtc.RepoHeaderComponentContext)
@repoBreadcrumbComponent(rtc.RepoBreadcrumbComponentContext)
@repoTreeComponent(rtc.RepoTreeComponentContext)
@readmeComponent(rtc.ReadmeComponentContext)
}
}
type RepoTreeComponentContext struct {
Repo string
Ref string
Tree []git.FileInfo
Back string
}
func slashDir(name string, isDir bool) string {
if isDir {
return name + "/"
}
return name
}
templ repoTreeComponent(rtcc RepoTreeComponentContext) {
<div class="grid grid-cols-3 sm:grid-cols-8 text-text py-5 rounded px-5 gap-x-3 gap-y-1 bg-base dark:bg-base/50">
if rtcc.Back != "" {
<div class="col-span-2"></div>
<div class="sm:col-span-6"><a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rtcc.Repo, rtcc.Ref, rtcc.Back)) }>..</a></div>
}
for _, fi := range rtcc.Tree {
<div class="sm:col-span-1 break-keep">{ fi.Mode }</div>
<div class="sm:col-span-1 text-right">{ fi.Size }</div>
<div class="sm:col-span-6 overflow-hidden text-ellipsis"><a class="underline decoration-text/50 decoration-dashed hover:decoration-solid" href={ templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rtcc.Repo, rtcc.Ref, fi.Path)) }>{ slashDir(fi.Name(), fi.IsDir) }</a></div>
}
</div>
}

View File

@ -1,199 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package html
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"fmt"
"go.jolheiser.com/ugit/internal/git"
)
type RepoTreeContext struct {
BaseContext
RepoHeaderComponentContext
RepoBreadcrumbComponentContext
RepoTreeComponentContext
ReadmeComponentContext
Description string
}
func RepoTree(rtc RepoTreeContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
templ_7745c5c3_Err = repoHeaderComponent(rtc.RepoHeaderComponentContext).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = repoBreadcrumbComponent(rtc.RepoBreadcrumbComponentContext).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = repoTreeComponent(rtc.RepoTreeComponentContext).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = readmeComponent(rtc.ReadmeComponentContext).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = base(rtc.BaseContext).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
type RepoTreeComponentContext struct {
Repo string
Ref string
Tree []git.FileInfo
Back string
}
func slashDir(name string, isDir bool) string {
if isDir {
return name + "/"
}
return name
}
func repoTreeComponent(rtcc RepoTreeComponentContext) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"grid grid-cols-3 sm:grid-cols-8 text-text py-5 rounded px-5 gap-x-3 gap-y-1 bg-base dark:bg-base/50\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if rtcc.Back != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"col-span-2\"></div><div class=\"sm:col-span-6\"><a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rtcc.Repo, rtcc.Ref, rtcc.Back))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">..</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
for _, fi := range rtcc.Tree {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"sm:col-span-1 break-keep\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fi.Mode)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_tree.templ`, Line: 47, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"sm:col-span-1 text-right\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fi.Size)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_tree.templ`, Line: 48, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"sm:col-span-6 overflow-hidden text-ellipsis\"><a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/%s/tree/%s/%s", rtcc.Repo, rtcc.Ref, fi.Path))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(slashDir(fi.Name(), fi.IsDir))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_tree.templ`, Line: 49, Col: 256}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./**/*.templ"],
content: ["./*.go"],
plugins: [require("@catppuccin/tailwindcss")],
}

File diff suppressed because one or more lines are too long

View File

@ -126,7 +126,7 @@ func (rh repoHandler) repoHeaderContext(repo *git.Repo, r *http.Request) html.Re
Name: chi.URLParam(r, "repo"),
Ref: ref,
CloneURL: rh.s.CloneURL,
Tags: repo.Meta.Tags,
Tags: repo.Meta.Tags.Slice(),
}
}

View File

@ -2,9 +2,8 @@ package httperr
import (
"errors"
"log/slog"
"net/http"
"github.com/charmbracelet/log"
)
type httpError struct {
@ -41,7 +40,7 @@ func Handler(fn func(w http.ResponseWriter, r *http.Request) error) http.Handler
status = httpErr.status
}
log.Error(err)
slog.Error("httperr Handler error", "error", err)
http.Error(w, http.StatusText(status), status)
}
}

View File

@ -0,0 +1,122 @@
package httperr_test
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/alecthomas/assert/v2"
"go.jolheiser.com/ugit/internal/http/httperr"
)
func successHandler(w http.ResponseWriter, r *http.Request) error {
w.WriteHeader(http.StatusOK)
return nil
}
func errorHandler(w http.ResponseWriter, r *http.Request) error {
return errors.New("test error")
}
func statusErrorHandler(status int) func(w http.ResponseWriter, r *http.Request) error {
return func(w http.ResponseWriter, r *http.Request) error {
return httperr.Status(errors.New("test error"), status)
}
}
func TestHandler_Success(t *testing.T) {
handler := httperr.Handler(successHandler)
req := httptest.NewRequest("GET", "/", nil)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code)
}
func TestHandler_Error(t *testing.T) {
handler := httperr.Handler(errorHandler)
req := httptest.NewRequest("GET", "/", nil)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusInternalServerError, recorder.Code)
}
func TestHandler_StatusError(t *testing.T) {
testCases := []struct {
name string
status int
expectedStatus int
}{
{
name: "not found",
status: http.StatusNotFound,
expectedStatus: http.StatusNotFound,
},
{
name: "bad request",
status: http.StatusBadRequest,
expectedStatus: http.StatusBadRequest,
},
{
name: "unauthorized",
status: http.StatusUnauthorized,
expectedStatus: http.StatusUnauthorized,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
handler := httperr.Handler(statusErrorHandler(tc.status))
req := httptest.NewRequest("GET", "/", nil)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
assert.Equal(t, tc.expectedStatus, recorder.Code)
})
}
}
type unwrapper interface {
Unwrap() error
}
func TestError(t *testing.T) {
originalErr := errors.New("original error")
httpErr := httperr.Error(originalErr)
assert.Equal(t, originalErr.Error(), httpErr.Error())
unwrapper, ok := any(httpErr).(unwrapper)
assert.True(t, ok)
assert.Equal(t, originalErr, unwrapper.Unwrap())
}
func TestStatus(t *testing.T) {
originalErr := errors.New("original error")
httpErr := httperr.Status(originalErr, http.StatusNotFound)
assert.Equal(t, originalErr.Error(), httpErr.Error())
unwrapper, ok := any(httpErr).(unwrapper)
assert.True(t, ok)
assert.Equal(t, originalErr, unwrapper.Unwrap())
handler := httperr.Handler(func(w http.ResponseWriter, r *http.Request) error {
return httpErr
})
req := httptest.NewRequest("GET", "/", nil)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusNotFound, recorder.Code)
}

View File

@ -3,7 +3,6 @@ package http
import (
"net/http"
"os"
"slices"
"sort"
"strings"
"time"
@ -34,10 +33,10 @@ func (rh repoHandler) index(w http.ResponseWriter, r *http.Request) error {
if !rh.s.ShowPrivate {
continue
}
repo.Meta.Tags = append(repo.Meta.Tags, "private")
repo.Meta.Tags.Add("private")
}
if tagFilter != "" && !slices.Contains(repo.Meta.Tags, strings.ToLower(tagFilter)) {
if tagFilter != "" && !repo.Meta.Tags.Contains(strings.ToLower(tagFilter)) {
continue
}
repos = append(repos, repo)
@ -61,7 +60,7 @@ func (rh repoHandler) index(w http.ResponseWriter, r *http.Request) error {
})
}
if err := html.Index(html.IndexContext{
if err := html.IndexTemplate(html.IndexContext{
BaseContext: rh.baseContext(),
Profile: html.IndexProfile{
Username: rh.s.Profile.Username,
@ -69,7 +68,7 @@ func (rh repoHandler) index(w http.ResponseWriter, r *http.Request) error {
Links: links,
},
Repos: repos,
}).Render(r.Context(), w); err != nil {
}).Render(w); err != nil {
return httperr.Error(err)
}

View File

@ -30,7 +30,7 @@ func (rh repoHandler) repoMiddleware(next http.Handler) http.Handler {
if !rh.s.ShowPrivate {
return httperr.Status(errors.New("could not get git repo"), http.StatusNotFound)
}
repo.Meta.Tags = append(repo.Meta.Tags, "private")
repo.Meta.Tags.Add("private")
}
r = r.WithContext(context.WithValue(r.Context(), repoCtxKey, repo))
next.ServeHTTP(w, r)

View File

@ -47,7 +47,7 @@ func (rh repoHandler) repoTree(ref, path string) http.HandlerFunc {
if path != "" {
back = filepath.Dir(path)
}
if err := html.RepoTree(html.RepoTreeContext{
if err := html.RepoTreeTemplate(html.RepoTreeContext{
Description: repo.Meta.Description,
BaseContext: rh.baseContext(),
RepoHeaderComponentContext: rh.repoHeaderContext(repo, r),
@ -61,7 +61,7 @@ func (rh repoHandler) repoTree(ref, path string) http.HandlerFunc {
ReadmeComponentContext: html.ReadmeComponentContext{
Markdown: readmeContent,
},
}).Render(r.Context(), w); err != nil {
}).Render(w); err != nil {
return httperr.Error(err)
}
@ -88,16 +88,26 @@ func (rh repoHandler) repoFile(w http.ResponseWriter, r *http.Request, repo *git
}
var buf bytes.Buffer
if err := markup.Code.Convert([]byte(content), filepath.Base(path), &buf); err != nil {
if err := markup.Convert([]byte(content), filepath.Base(path), "L", &buf); err != nil {
return httperr.Error(err)
}
if err := html.RepoFile(html.RepoFileContext{
commit := ref
if len(ref) < 40 {
commitObj, err := repo.GetCommitFromRef(ref)
if err == nil {
commit = commitObj.Hash.String()
}
}
if err := html.RepoFileTemplate(html.RepoFileContext{
BaseContext: rh.baseContext(),
RepoHeaderComponentContext: rh.repoHeaderContext(repo, r),
RepoBreadcrumbComponentContext: rh.repoBreadcrumbContext(repo, r, path),
Code: buf.String(),
}).Render(r.Context(), w); err != nil {
Commit: commit,
Path: path,
}).Render(w); err != nil {
return httperr.Error(err)
}
@ -117,12 +127,12 @@ func (rh repoHandler) repoRefs(w http.ResponseWriter, r *http.Request) error {
return httperr.Error(err)
}
if err := html.RepoRefs(html.RepoRefsContext{
if err := html.RepoRefsTemplate(html.RepoRefsContext{
BaseContext: rh.baseContext(),
RepoHeaderComponentContext: rh.repoHeaderContext(repo, r),
Branches: branches,
Tags: tags,
}).Render(r.Context(), w); err != nil {
}).Render(w); err != nil {
return httperr.Error(err)
}
@ -137,11 +147,11 @@ func (rh repoHandler) repoLog(w http.ResponseWriter, r *http.Request) error {
return httperr.Error(err)
}
if err := html.RepoLog(html.RepoLogContext{
if err := html.RepoLogTemplate(html.RepoLogContext{
BaseContext: rh.baseContext(),
RepoHeaderComponentContext: rh.repoHeaderContext(repo, r),
Commits: commits,
}).Render(r.Context(), w); err != nil {
}).Render(w); err != nil {
return httperr.Error(err)
}
@ -158,17 +168,17 @@ func (rh repoHandler) repoCommit(w http.ResponseWriter, r *http.Request) error {
for idx, p := range commit.Files {
var patch bytes.Buffer
if err := markup.Code.Basic([]byte(p.Patch), "commit.patch", &patch); err != nil {
if err := markup.Convert([]byte(p.Patch), "commit.patch", p.Path()+"-L", &patch); err != nil {
return httperr.Error(err)
}
commit.Files[idx].Patch = patch.String()
}
if err := html.RepoCommit(html.RepoCommitContext{
if err := html.RepoCommitTemplate(html.RepoCommitContext{
BaseContext: rh.baseContext(),
RepoHeaderComponentContext: rh.repoHeaderContext(repo, r),
Commit: commit,
}).Render(r.Context(), w); err != nil {
}).Render(w); err != nil {
return httperr.Error(err)
}
@ -206,14 +216,13 @@ func (rh repoHandler) repoSearch(w http.ResponseWriter, r *http.Request) error {
}
results[idx].Content = buf.String()
}
}
if err := html.RepoSearch(html.SearchContext{
if err := html.RepoSearchTemplate(html.SearchContext{
BaseContext: rh.baseContext(),
RepoHeaderComponentContext: rh.repoHeaderContext(repo, r),
Results: results,
}).Render(r.Context(), w); err != nil {
}).Render(w); err != nil {
return httperr.Error(err)
}

View File

@ -2,8 +2,8 @@ package ssh
import (
"fmt"
"log"
"github.com/charmbracelet/log"
"github.com/charmbracelet/ssh"
"github.com/charmbracelet/wish"
"github.com/charmbracelet/wish/logging"
@ -42,7 +42,7 @@ func (a hooks) Push(_ string, _ ssh.PublicKey) {}
func (a hooks) Fetch(_ string, _ ssh.PublicKey) {}
var (
DefaultLogger logging.Logger = log.StandardLog()
DefaultLogger logging.Logger = log.Default()
NoopLogger logging.Logger = noopLogger{}
)

View File

@ -5,14 +5,15 @@ import (
"errors"
"fmt"
"io/fs"
"log/slog"
"os"
"path/filepath"
"strings"
"text/tabwriter"
"go.jolheiser.com/ugit/internal/git"
"go.jolheiser.com/ugit/internal/tui"
"github.com/charmbracelet/log"
"github.com/charmbracelet/ssh"
"github.com/charmbracelet/wish"
)
@ -91,7 +92,7 @@ func Middleware(repoDir string, cloneURL string, port int, gh Hooks) wish.Middle
if errors.Is(err, ErrInvalidRepo) {
Fatal(s, ErrInvalidRepo)
}
log.Error("unknown git error", "error", err)
slog.Error("unknown git error", "error", err)
Fatal(s, ErrSystemMalfunction)
}
gh.Fetch(repo, pk)
@ -99,11 +100,15 @@ func Middleware(repoDir string, cloneURL string, port int, gh Hooks) wish.Middle
}
}
// Repo list
// No args, start TUI
if len(cmd) == 0 {
if err := tui.Start(s, repoDir); err != nil {
slog.Error("failed to start TUI", "error", err)
// Fall back to simple list on TUI error
des, err := os.ReadDir(repoDir)
if err != nil && err != fs.ErrNotExist {
log.Error("invalid repository", "error", err)
slog.Error("invalid repository", "error", err)
}
tw := tabwriter.NewWriter(s, 0, 0, 1, ' ', 0)
for _, de := range des {
@ -122,6 +127,8 @@ func Middleware(repoDir string, cloneURL string, port int, gh Hooks) wish.Middle
}
tw.Flush()
}
return
}
sh(s)
}
}

View File

@ -0,0 +1,230 @@
package tui
import (
"fmt"
"strings"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"go.jolheiser.com/ugit/internal/git"
)
type repoForm struct {
inputs []textinput.Model
isPrivate bool
focusIndex int
width int
height int
done bool
save bool
selectedRepo *git.Repo
}
// newRepoForm creates a new repository editing form
func newRepoForm() repoForm {
var inputs []textinput.Model
nameInput := textinput.New()
nameInput.Placeholder = "Repository name"
nameInput.Focus()
nameInput.Width = 50
inputs = append(inputs, nameInput)
descInput := textinput.New()
descInput.Placeholder = "Repository description"
descInput.Width = 50
inputs = append(inputs, descInput)
tagsInput := textinput.New()
tagsInput.Placeholder = "Tags (comma separated)"
tagsInput.Width = 50
inputs = append(inputs, tagsInput)
return repoForm{
inputs: inputs,
focusIndex: 0,
}
}
// setValues sets the form values from the selected repo
func (f *repoForm) setValues(repo *git.Repo) {
f.inputs[0].SetValue(repo.Name())
f.inputs[1].SetValue(repo.Meta.Description)
f.inputs[2].SetValue(strings.Join(repo.Meta.Tags.Slice(), ", "))
f.isPrivate = repo.Meta.Private
f.inputs[0].Focus()
f.focusIndex = 0
}
// setSize sets the form dimensions
func (f *repoForm) setSize(width, height int) {
f.width = width
f.height = height
for i := range f.inputs {
f.inputs[i].Width = width - 10
}
}
// isPrivateToggleFocused returns true if the private toggle is focused
func (f *repoForm) isPrivateToggleFocused() bool {
return f.focusIndex == len(f.inputs)
}
// isSaveButtonFocused returns true if the save button is focused
func (f *repoForm) isSaveButtonFocused() bool {
return f.focusIndex == len(f.inputs)+1
}
// isCancelButtonFocused returns true if the cancel button is focused
func (f *repoForm) isCancelButtonFocused() bool {
return f.focusIndex == len(f.inputs)+2
}
// Update handles form updates
func (f repoForm) Update(msg tea.Msg) (repoForm, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "tab", "shift+tab", "up", "down":
if msg.String() == "up" || msg.String() == "shift+tab" {
f.focusIndex--
if f.focusIndex < 0 {
f.focusIndex = len(f.inputs) + 3 - 1
}
} else {
f.focusIndex++
if f.focusIndex >= len(f.inputs)+3 {
f.focusIndex = 0
}
}
for i := range f.inputs {
if i == f.focusIndex {
cmds = append(cmds, f.inputs[i].Focus())
} else {
f.inputs[i].Blur()
}
}
case "enter":
if f.isSaveButtonFocused() {
f.done = true
f.save = true
return f, nil
}
if f.isCancelButtonFocused() {
f.done = true
f.save = false
return f, nil
}
case "esc":
f.done = true
f.save = false
return f, nil
case " ":
if f.isPrivateToggleFocused() {
f.isPrivate = !f.isPrivate
}
if f.isSaveButtonFocused() {
f.done = true
f.save = true
return f, nil
}
if f.isCancelButtonFocused() {
f.done = true
f.save = false
return f, nil
}
}
}
for i := range f.inputs {
if i == f.focusIndex {
var cmd tea.Cmd
f.inputs[i], cmd = f.inputs[i].Update(msg)
cmds = append(cmds, cmd)
}
}
return f, tea.Batch(cmds...)
}
// View renders the form
func (f repoForm) View() string {
var b strings.Builder
formStyle := lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("170")).
Padding(1, 2)
titleStyle := lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("170")).
MarginBottom(1)
b.WriteString(titleStyle.Render("Edit Repository"))
b.WriteString("\n\n")
b.WriteString("Repository Name:\n")
b.WriteString(f.inputs[0].View())
b.WriteString("\n\n")
b.WriteString("Description:\n")
b.WriteString(f.inputs[1].View())
b.WriteString("\n\n")
b.WriteString("Tags (comma separated):\n")
b.WriteString(f.inputs[2].View())
b.WriteString("\n\n")
toggleStyle := lipgloss.NewStyle()
if f.isPrivateToggleFocused() {
toggleStyle = toggleStyle.Foreground(lipgloss.Color("170")).Bold(true)
}
visibility := "Public 🔓"
if f.isPrivate {
visibility = "Private 🔒"
}
b.WriteString(toggleStyle.Render(fmt.Sprintf("[%s] %s", visibility, "Toggle with Space")))
b.WriteString("\n\n")
buttonStyle := lipgloss.NewStyle().
Padding(0, 3).
MarginRight(1)
focusedButtonStyle := buttonStyle.Copy().
Foreground(lipgloss.Color("0")).
Background(lipgloss.Color("170")).
Bold(true)
saveButton := buttonStyle.Render("[ Save ]")
cancelButton := buttonStyle.Render("[ Cancel ]")
if f.isSaveButtonFocused() {
saveButton = focusedButtonStyle.Render("[ Save ]")
}
if f.isCancelButtonFocused() {
cancelButton = focusedButtonStyle.Render("[ Cancel ]")
}
b.WriteString(saveButton + cancelButton)
b.WriteString("\n\n")
b.WriteString("\nTab: Next • Shift+Tab: Previous • Enter: Select • Esc: Cancel")
return formStyle.Width(f.width - 4).Render(b.String())
}

View File

@ -0,0 +1,65 @@
package tui
import (
"github.com/charmbracelet/bubbles/key"
)
// keyMap defines the keybindings for the TUI
type keyMap struct {
Up key.Binding
Down key.Binding
Edit key.Binding
Delete key.Binding
Help key.Binding
Quit key.Binding
Confirm key.Binding
Cancel key.Binding
}
// ShortHelp returns keybindings to be shown in the mini help view.
func (k keyMap) ShortHelp() []key.Binding {
return []key.Binding{k.Help, k.Edit, k.Delete, k.Quit}
}
// FullHelp returns keybindings for the expanded help view.
func (k keyMap) FullHelp() [][]key.Binding {
return [][]key.Binding{
{k.Up, k.Down, k.Edit},
{k.Delete, k.Help, k.Quit},
}
}
var keys = keyMap{
Up: key.NewBinding(
key.WithKeys("up", "k"),
key.WithHelp("↑/k", "up"),
),
Down: key.NewBinding(
key.WithKeys("down", "j"),
key.WithHelp("↓/j", "down"),
),
Edit: key.NewBinding(
key.WithKeys("e"),
key.WithHelp("e", "edit"),
),
Delete: key.NewBinding(
key.WithKeys("d"),
key.WithHelp("d", "delete"),
),
Help: key.NewBinding(
key.WithKeys("?"),
key.WithHelp("?", "help"),
),
Quit: key.NewBinding(
key.WithKeys("q", "ctrl+c"),
key.WithHelp("q", "quit"),
),
Confirm: key.NewBinding(
key.WithKeys("y"),
key.WithHelp("y", "confirm"),
),
Cancel: key.NewBinding(
key.WithKeys("n", "esc"),
key.WithHelp("n", "cancel"),
),
}

View File

@ -0,0 +1,50 @@
package tui
import (
"fmt"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// Run runs the TUI standalone, useful for development or local usage
func Run(repoDir string) error {
model := Model{
repoDir: repoDir,
help: help.New(),
keys: keys,
activeView: ViewList,
repoForm: newRepoForm(),
}
repos, err := loadRepos(repoDir)
if err != nil {
return fmt.Errorf("failed to load repos: %w", err)
}
model.repos = repos
items := make([]list.Item, len(repos))
for i, repo := range repos {
items[i] = repoItem{repo: repo}
}
delegate := list.NewDefaultDelegate()
delegate.Styles.SelectedTitle = delegate.Styles.SelectedTitle.Foreground(lipgloss.Color("170"))
delegate.Styles.SelectedDesc = delegate.Styles.SelectedDesc.Foreground(lipgloss.Color("244"))
repoList := list.New(items, delegate, 0, 0)
repoList.Title = "Git Repositories"
repoList.SetShowStatusBar(true)
repoList.SetFilteringEnabled(true)
repoList.Styles.Title = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("170")).Padding(0, 0, 0, 2)
repoList.StatusMessageLifetime = 3
model.repoList = repoList
p := tea.NewProgram(model, tea.WithAltScreen(), tea.WithMouseCellMotion())
_, err = p.Run()
return err
}

View File

@ -0,0 +1,72 @@
package tui
import (
"strings"
"go.jolheiser.com/ugit/internal/git"
)
// repoItem represents a repository item in the list
type repoItem struct {
repo *git.Repo
}
// Title returns the title for the list item
func (r repoItem) Title() string {
return r.repo.Name()
}
// Description returns the description for the list item
func (r repoItem) Description() string {
var builder strings.Builder
if r.repo.Meta.Private {
builder.WriteString("🔒")
} else {
builder.WriteString("🔓")
}
builder.WriteString(" • ")
if r.repo.Meta.Description != "" {
builder.WriteString(r.repo.Meta.Description)
} else {
builder.WriteString("No description")
}
builder.WriteString(" • ")
builder.WriteString("[")
if len(r.repo.Meta.Tags) > 0 {
builder.WriteString(strings.Join(r.repo.Meta.Tags.Slice(), ", "))
}
builder.WriteString("]")
builder.WriteString(" • ")
lastCommit, err := r.repo.LastCommit()
if err == nil {
builder.WriteString(lastCommit.Short())
} else {
builder.WriteString("deadbeef")
}
return builder.String()
}
// FilterValue returns the value to use for filtering
func (r repoItem) FilterValue() string {
var builder strings.Builder
builder.WriteString(r.repo.Name())
builder.WriteString(" ")
builder.WriteString(r.repo.Meta.Description)
if len(r.repo.Meta.Tags) > 0 {
for _, tag := range r.repo.Meta.Tags.Slice() {
builder.WriteString(" ")
builder.WriteString(tag)
}
}
return strings.ToLower(builder.String())
}

310
internal/tui/tui.go 100644
View File

@ -0,0 +1,310 @@
package tui
import (
"fmt"
"log/slog"
"path/filepath"
"strings"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/ssh"
"github.com/charmbracelet/wish/bubbletea"
"go.jolheiser.com/ugit/internal/git"
)
// Model is the main TUI model
type Model struct {
repoList list.Model
repos []*git.Repo
repoDir string
width int
height int
help help.Model
keys keyMap
activeView View
repoForm repoForm
session ssh.Session
}
// View represents the current active view in the TUI
type View int
const (
ViewList View = iota
ViewForm
ViewConfirmDelete
)
// New creates a new TUI model
func New(s ssh.Session, repoDir string) (*Model, error) {
repos, err := loadRepos(repoDir)
if err != nil {
return nil, fmt.Errorf("failed to load repos: %w", err)
}
r := bubbletea.MakeRenderer(s)
items := make([]list.Item, len(repos))
for i, repo := range repos {
items[i] = repoItem{repo: repo}
}
delegate := list.NewDefaultDelegate()
delegate.Styles.SelectedTitle = delegate.Styles.SelectedTitle.Foreground(lipgloss.Color("170"))
delegate.Styles.SelectedDesc = delegate.Styles.SelectedDesc.Foreground(lipgloss.Color("244"))
repoList := list.New(items, delegate, 0, 0)
repoList.Title = "Git Repositories"
repoList.SetShowStatusBar(true)
repoList.SetFilteringEnabled(true)
repoList.Styles.Title = r.NewStyle().Bold(true).Foreground(lipgloss.Color("170")).Padding(0, 0, 0, 2)
repoList.StatusMessageLifetime = 3
repoList.Styles.Title.Renderer(r)
repoList.FilterInput.Placeholder = "Type to filter repositories..."
repoList.FilterInput.PromptStyle = r.NewStyle().Foreground(lipgloss.Color("170"))
repoList.FilterInput.TextStyle = r.NewStyle()
help := help.New()
repoForm := newRepoForm()
return &Model{
repoList: repoList,
repos: repos,
repoDir: repoDir,
help: help,
keys: keys,
activeView: ViewList,
repoForm: repoForm,
session: s,
}, nil
}
// loadRepos loads all git repositories from the given directory
func loadRepos(repoDir string) ([]*git.Repo, error) {
entries, err := git.ListRepos(repoDir)
if err != nil {
return nil, err
}
repos := make([]*git.Repo, 0, len(entries))
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".git") {
continue
}
repo, err := git.NewRepo(repoDir, entry.Name())
if err != nil {
slog.Error("error loading repo", "name", entry.Name(), "error", err)
continue
}
repos = append(repos, repo)
}
return repos, nil
}
// Init initializes the model
func (m Model) Init() tea.Cmd {
return nil
}
// Update handles all the messages and updates the model accordingly
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keys.Quit):
return m, tea.Quit
case key.Matches(msg, m.keys.Help):
m.help.ShowAll = !m.help.ShowAll
}
switch m.activeView {
case ViewList:
var cmd tea.Cmd
m.repoList, cmd = m.repoList.Update(msg)
cmds = append(cmds, cmd)
if m.repoList.FilterState() == list.Filtering {
break
}
switch {
case key.Matches(msg, m.keys.Edit):
if len(m.repos) == 0 {
m.repoList.NewStatusMessage("No repositories to edit")
break
}
selectedItem := m.repoList.SelectedItem().(repoItem)
m.repoForm.selectedRepo = selectedItem.repo
m.repoForm.setValues(selectedItem.repo)
m.activeView = ViewForm
return m, textinput.Blink
case key.Matches(msg, m.keys.Delete):
if len(m.repos) == 0 {
m.repoList.NewStatusMessage("No repositories to delete")
break
}
m.activeView = ViewConfirmDelete
}
case ViewForm:
var cmd tea.Cmd
m.repoForm, cmd = m.repoForm.Update(msg)
cmds = append(cmds, cmd)
if m.repoForm.done {
if m.repoForm.save {
selectedRepo := m.repoForm.selectedRepo
repoDir := filepath.Dir(selectedRepo.Path())
oldName := selectedRepo.Name()
newName := m.repoForm.inputs[0].Value()
var renamed bool
if oldName != newName {
if err := git.RenameRepo(repoDir, oldName, newName); err != nil {
m.repoList.NewStatusMessage(fmt.Sprintf("Error renaming repo: %s", err))
} else {
m.repoList.NewStatusMessage(fmt.Sprintf("Repository renamed from %s to %s", oldName, newName))
renamed = true
}
}
if renamed {
if newRepo, err := git.NewRepo(repoDir, newName+".git"); err == nil {
selectedRepo = newRepo
} else {
m.repoList.NewStatusMessage(fmt.Sprintf("Error loading renamed repo: %s", err))
}
}
selectedRepo.Meta.Description = m.repoForm.inputs[1].Value()
selectedRepo.Meta.Private = m.repoForm.isPrivate
tags := make(git.TagSet)
for _, tag := range strings.Split(m.repoForm.inputs[2].Value(), ",") {
tag = strings.TrimSpace(tag)
if tag != "" {
tags.Add(tag)
}
}
selectedRepo.Meta.Tags = tags
if err := selectedRepo.SaveMeta(); err != nil {
m.repoList.NewStatusMessage(fmt.Sprintf("Error saving repo metadata: %s", err))
} else if !renamed {
m.repoList.NewStatusMessage("Repository updated successfully")
}
}
m.repoForm.done = false
m.repoForm.save = false
m.activeView = ViewList
if repos, err := loadRepos(m.repoDir); err == nil {
m.repos = repos
items := make([]list.Item, len(repos))
for i, repo := range repos {
items[i] = repoItem{repo: repo}
}
m.repoList.SetItems(items)
}
}
case ViewConfirmDelete:
switch {
case key.Matches(msg, m.keys.Confirm):
selectedItem := m.repoList.SelectedItem().(repoItem)
repo := selectedItem.repo
if err := git.DeleteRepo(repo.Path()); err != nil {
m.repoList.NewStatusMessage(fmt.Sprintf("Error deleting repo: %s", err))
} else {
m.repoList.NewStatusMessage(fmt.Sprintf("Repository %s deleted", repo.Name()))
if repos, err := loadRepos(m.repoDir); err == nil {
m.repos = repos
items := make([]list.Item, len(repos))
for i, repo := range repos {
items[i] = repoItem{repo: repo}
}
m.repoList.SetItems(items)
}
}
m.activeView = ViewList
case key.Matches(msg, m.keys.Cancel):
m.activeView = ViewList
}
}
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
headerHeight := 3
footerHeight := 2
m.repoList.SetSize(msg.Width, msg.Height-headerHeight-footerHeight)
m.repoForm.setSize(msg.Width, msg.Height)
m.help.Width = msg.Width
}
return m, tea.Batch(cmds...)
}
// View renders the current UI
func (m Model) View() string {
switch m.activeView {
case ViewList:
return fmt.Sprintf("%s\n%s", m.repoList.View(), m.help.View(m.keys))
case ViewForm:
return m.repoForm.View()
case ViewConfirmDelete:
selectedItem := m.repoList.SelectedItem().(repoItem)
repo := selectedItem.repo
confirmStyle := lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("170")).
Padding(1, 2).
Width(m.width - 4).
Align(lipgloss.Center)
confirmText := fmt.Sprintf(
"Are you sure you want to delete repository '%s'?\n\nThis action cannot be undone!\n\nPress y to confirm or n to cancel.",
repo.Name(),
)
return confirmStyle.Render(confirmText)
}
return ""
}
// Start runs the TUI
func Start(s ssh.Session, repoDir string) error {
model, err := New(s, repoDir)
if err != nil {
return err
}
p := tea.NewProgram(model, tea.WithAltScreen(), tea.WithMouseCellMotion(), tea.WithInput(s), tea.WithOutput(s))
_, err = p.Run()
return err
}

10
nix/default.nix 100644
View File

@ -0,0 +1,10 @@
{
pkgs ? import <nixpkgs> { },
}:
let
pkg = pkgs.callPackage ./pkg.nix { inherit pkgs; };
in
{
ugit = pkg;
default = pkg;
}

228
nix/module.nix 100644
View File

@ -0,0 +1,228 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.services.ugit;
pkg = pkgs.callPackage ./pkg.nix { inherit pkgs; };
yamlFormat = pkgs.formats.yaml { };
instanceOptions =
{ name, config, ... }:
let
inherit (lib) mkEnableOption mkOption types;
baseDir = "/var/lib/ugit-${name}";
in
{
options = {
enable = mkEnableOption "Enable ugit";
package = mkOption {
type = types.package;
description = "ugit package to use";
default = pkg;
};
homeDir = mkOption {
type = types.str;
description = "ugit home directory";
default = baseDir;
};
repoDir = mkOption {
type = types.str;
description = "where ugit stores repositories";
default = "${baseDir}/repos";
};
authorizedKeys = mkOption {
type = types.listOf types.str;
description = "list of keys to use for authorized_keys";
default = [ ];
};
authorizedKeysFile = mkOption {
type = types.str;
description = "path to authorized_keys file ugit uses for auth";
default = "${baseDir}/authorized_keys";
};
hostKeyFile = mkOption {
type = types.str;
description = "path to host key file (will be created if it doesn't exist)";
default = "${baseDir}/ugit_ed25519";
};
config = mkOption {
type = types.attrs;
default = { };
description = "config.yaml contents";
};
user = mkOption {
type = types.str;
default = "ugit-${name}";
description = "User account under which ugit runs";
};
group = mkOption {
type = types.str;
default = "ugit-${name}";
description = "Group account under which ugit runs";
};
hooks = mkOption {
type = types.listOf (
types.submodule {
options = {
name = mkOption {
type = types.str;
description = "Hook name";
};
content = mkOption {
type = types.str;
description = "Hook contents";
};
};
}
);
description = "A list of pre-receive hooks to run";
default = [ ];
};
};
};
in
{
options = {
services.ugit = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule instanceOptions);
default = { };
description = "Attribute set of ugit instances";
};
};
config = lib.mkIf (cfg != { }) {
users.users = lib.mapAttrs' (
name: instanceCfg:
lib.nameValuePair instanceCfg.user {
home = instanceCfg.homeDir;
createHome = true;
group = instanceCfg.group;
isSystemUser = true;
isNormalUser = false;
description = "user for ugit ${name} service";
}
) (lib.filterAttrs (name: instanceCfg: instanceCfg.enable) cfg);
users.groups = lib.mapAttrs' (name: instanceCfg: lib.nameValuePair instanceCfg.group { }) (
lib.filterAttrs (name: instanceCfg: instanceCfg.enable) cfg
);
systemd.services = lib.foldl' (
acc: name:
let
instanceCfg = cfg.${name};
in
lib.recursiveUpdate acc (
lib.optionalAttrs instanceCfg.enable {
"ugit-${name}" = {
enable = true;
description = "ugit instance ${name}";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = [
instanceCfg.package
pkgs.git
pkgs.bash
];
serviceConfig = {
User = instanceCfg.user;
Group = instanceCfg.group;
Restart = "always";
RestartSec = "15";
WorkingDirectory = instanceCfg.homeDir;
ReadWritePaths = [ instanceCfg.homeDir ];
CapabilityBoundingSet = "";
NoNewPrivileges = true;
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = true;
PrivateUsers = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
RemoveIPC = true;
PrivateMounts = true;
SystemCallArchitectures = "native";
ExecStart =
let
configFile = pkgs.writeText "ugit-${name}.yaml" (
builtins.readFile (yamlFormat.generate "ugit-${name}-yaml" instanceCfg.config)
);
authorizedKeysFile = pkgs.writeText "ugit_${name}_keys" (
builtins.concatStringsSep "\n" instanceCfg.authorizedKeys
);
authorizedKeysPath =
if (builtins.length instanceCfg.authorizedKeys) > 0 then
authorizedKeysFile
else
instanceCfg.authorizedKeysFile;
args = [
"--config=${configFile}"
"--repo-dir=${instanceCfg.repoDir}"
"--ssh.authorized-keys=${authorizedKeysPath}"
"--ssh.host-key=${instanceCfg.hostKeyFile}"
];
in
"${instanceCfg.package}/bin/ugitd ${builtins.concatStringsSep " " args}";
};
};
"ugit-${name}-hooks" = {
description = "Setup hooks for ugit instance ${name}";
wantedBy = [ "multi-user.target" ];
after = [ "ugit-${name}.service" ];
requires = [ "ugit-${name}.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = instanceCfg.user;
Group = instanceCfg.group;
ExecStart =
let
hookDir = "${instanceCfg.repoDir}/hooks/pre-receive.d";
mkHookScript =
hook:
let
script = pkgs.writeShellScript "ugit-${name}-${hook.name}" hook.content;
in
''
mkdir -p ${hookDir}
ln -sf ${script} ${hookDir}/${hook.name}
'';
in
pkgs.writeShellScript "ugit-${name}-hooks-setup" ''
${builtins.concatStringsSep "\n" (map mkHookScript instanceCfg.hooks)}
'';
};
};
}
)
) { } (builtins.attrNames cfg);
};
}

32
nix/pkg.nix 100644
View File

@ -0,0 +1,32 @@
{
pkgs ? import <nixpkgs> { },
}:
let
name = "ugitd";
in
pkgs.buildGoModule {
pname = name;
version = "main";
src = pkgs.nix-gitignore.gitignoreSource [ ] (
builtins.path {
inherit name;
path = ../.;
}
);
subPackages = [
"cmd/ugitd"
];
vendorHash = pkgs.lib.fileContents ../go.mod.sri;
env.CGO_ENABLED = 0;
flags = [ "-trimpath" ];
ldflags = [
"-s"
"-w"
"-extldflags -static"
];
meta = {
description = "Minimal git server";
homepage = "https://git.jolheiser.com/ugit";
mainProgram = "ugitd";
};
}

84
nix/vm.nix 100644
View File

@ -0,0 +1,84 @@
{ pkgs, ... }:
let
privKey = ''
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBIpmLtcHhECei1ls6s0kKUehjpRCP9yel/c5YCIb5DpQAAAIgAYtkzAGLZ
MwAAAAtzc2gtZWQyNTUxOQAAACBIpmLtcHhECei1ls6s0kKUehjpRCP9yel/c5YCIb5DpQ
AAAEDFY3M69VfnFbyE67r3l4lDcf5eht5qgNemE9xtMhRkBkimYu1weEQJ6LWWzqzSQpR6
GOlEI/3J6X9zlgIhvkOlAAAAAAECAwQF
-----END OPENSSH PRIVATE KEY-----
'';
pubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEimYu1weEQJ6LWWzqzSQpR6GOlEI/3J6X9zlgIhvkOl";
sshConfig = ''
Host ugit
HostName localhost
Port 8448
User ugit
IdentityFile ~/.ssh/vm
IdentitiesOnly yes
'';
in
{
imports = [ ./module.nix ];
environment.systemPackages = with pkgs; [ git ];
services.getty.autologinUser = "root";
services.openssh.enable = true;
services.ugit.vm = {
enable = true;
authorizedKeys = [ pubKey ];
hooks = [
{
name = "pre-receive";
content = ''
echo "Pre-receive hook executed"
'';
}
];
};
systemd.services."setup-vm" = {
wantedBy = [ "multi-user.target" ];
after = [ "ugit-vm.service" ];
path = with pkgs; [
git
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = "root";
Group = "root";
ExecStart =
let
privSSH = pkgs.writeText "vm-privkey" privKey;
sshConfigFile = pkgs.writeText "vm-sshconfig" sshConfig;
in
pkgs.writeShellScript "setup-vm-script" ''
# Hack to let ugit start up and generate its SSH keypair
sleep 3
# Set up git
git config --global user.name "NixUser"
git config --global user.email "nixuser@example.com"
git config --global init.defaultBranch main
git config --global push.autoSetupRemote true
# Set up SSH files
mkdir ~/.ssh
ln -sf ${sshConfigFile} ~/.ssh/config
cp ${privSSH} ~/.ssh/vm
chmod 600 ~/.ssh/vm
echo "[localhost]:8448 $(cat /var/lib/ugit-vm/ugit_ed25519.pub)" > ~/.ssh/known_hosts
# Stage some git activity
mkdir ~/repo
cd ~/repo
git init
git remote add origin ugit:repo.git
touch README.md
git add README.md
git commit -m "Test"
'';
};
};
}