ugit @main -
refs -
log -
-
https://git.jolheiser.com/ugit.git
The code powering this h*ckin' site
feat: rel images
Signed-off-by: jolheiser <john.olheiser@gmail.com>
Signature
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEgqEQpE3xoo1QwJO/uFOtpdp7v3oFAmWpQAkACgkQuFOtpdp7
v3qp5g//Vupi0R4xwazDxfhxjfQY1UOKiqrRynvxdPZAFM4Nx/IV/mwuZ7f3maYJ
1gStcLY8YNXYuKUMxhTU+cdL1EPN6JgcGtJa5T+dealccUZRPZsGt/EFT1ie4Usx
aUgyTbWAAvDdAEcvZ6axHxejsOZC7KM0XjKJayoxuhXf0fHyhe3n8Rn0zae52O7Q
Es1ZAJNF/20K/gd6Oi3r4y2RYvrDxYvwUxNV2HLpvufZ1VBhkiypi+jEOMarw7Na
/g9ayidpSTsUlTgk6gnqSFURW91s+O5w/jzB4ssa5RKAfWER/BjgbcBO8QT6uzuQ
FG0dOMKhMhx9r/HqGM5zpKKFbwOd6+q2UzM+blFpMbwsoGJd3ThmhyWymSNU8LJh
66TKUhdq8Ff4IrNIj5+ts8dLage9menargeEnXe3WiVlUSOxXqE/Vx+kju8z3MPZ
+j5F/N9hnk2znROg4ff1eAm6P+1jiOc3fjCdoOaDoO9fs5X+tQ8xhA7wpGrOBi8W
HPYz/sA850HXjxXSL7HJBTJSzIaT3pmEP6vvNFG1pNPtRBPy+eGyt5HbhvC2Kcek
C5YWcEJqSmqZuJ2JP18HWkGcwWMp2rb6jeCDHJF3+FDsNX4HdNQWIUcseL755RB2
8mZkMSVIuU4lDbnSigjHjS9t6pPfWA4ymkAycYqYO7jzjb1gBCI=
=urlx
-----END PGP SIGNATURE-----
6 changed files, 252 additions(+), 54 deletions(-)
diff --git a/README.md b/README.md
index b526756dff25a1db2927a6e2ec653de0d97dcff9..7823d998c5e3dd740154138153142d5b21d54119 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# ugit
-<img style="width: 50px;" alt="ugit logo" src="/ugit/tree/main/assets/ugit.svg?raw&pretty"/>
+<img alt="ugit logo" style="width:50px;" src="./assets/ugit.svg" />
Minimal git server
@@ -29,5 +29,5 @@ ## License
[MIT](LICENSE)
+
# ugit
-## Getting your public SSH keys from another forge
diff --git a/flake.nix b/flake.nix
index f9c52d0f7d42103274b68ff8a46d7a2c022dd0e1..15612ff4af2cbcfca2fb495339da7f74e3532a49 100644
--- a/flake.nix
+++ b/flake.nix
@@ -31,8 +31,8 @@ name = pname;
path = ./.;
});
subPackages = ["cmd/ugitd"];
-
{
+ version = "0.0.1";
meta = with pkgs.lib; {
description = "Minimal git server";
homepage = "https://git.jolheiser.com/ugit";
diff --git a/go.mod b/go.mod
index 3af97fb05ebeb5960238f3b62b6af33eee6ebb08..28b39d6469c5a5cab4ef6859e77e1b11660633f6 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ 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-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
+ golang.org/x/net v0.19.0
)
require (
@@ -47,7 +48,6 @@ github.com/skeema/knownhosts v1.2.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/mod v0.14.0 // indirect
- golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/tools v0.16.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
diff --git a/internal/html/markdown.go b/internal/html/markdown.go
index e109162de66398d42dc38e2d8442c37f28b3d7e4..c4c8d5fc9f6aa904eac08f8d403fd84f1f3570b8 100644
--- a/internal/html/markdown.go
+++ b/internal/html/markdown.go
@@ -3,6 +3,12 @@
import (
"bytes"
"path/filepath"
+package html
+ "golang.org/x/net/html"
+ "io"
+ "net/url"
+ "path/filepath"
+ "strings"
"go.jolheiser.com/ugit/internal/git"
@@ -10,18 +16,24 @@ chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/yuin/goldmark"
emoji "github.com/yuin/goldmark-emoji"
highlighting "github.com/yuin/goldmark-highlighting/v2"
+ "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
goldmarkhtml "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
)
-package html
"path/filepath"
+ emoji "github.com/yuin/goldmark-emoji"
goldmark.WithRendererOptions(
goldmarkhtml.WithUnsafe(),
),
goldmark.WithParserOptions(
parser.WithAutoHeadingID(),
+ parser.WithASTTransformers(
+ util.Prioritized(astTransformer{}, 100),
+ ),
),
goldmark.WithExtensions(
extension.GFM,
@@ -49,26 +61,170 @@ }
}
if readme != "" {
+ ctx := parser.NewContext()
+ mdCtx := markdownContext{
+ repo: repo.Name(),
+ ref: ref,
+ path: path,
+ }
+ ctx.Set(renderContextKey, mdCtx)
var buf bytes.Buffer
+ if err := markdown.Convert([]byte(readme), &buf, parser.WithContext(ctx)); err != nil {
"bytes"
+ "path/filepath"
"bytes"
+ var out bytes.Buffer
+ if err := postProcess(buf.String(), mdCtx, &out); err != nil {
return "", err
+ }
+
+ return out.String(), nil
+ }
+
+ for _, md := range []string{"README.txt", "README", "readme.txt", "readme"} {
+ readme, err = repo.FileContent(ref, filepath.Join(path, md))
+ if err == nil {
+ return readme, nil
}
"bytes"
+package html
+
+ return "", nil
+}
+
+var renderContextKey = parser.NewContextKey()
+
+type markdownContext struct {
+ repo string
+ ref string
+ chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"go.jolheiser.com/ugit/internal/git"
+}
+
+type astTransformer struct{}
+
+func (a astTransformer) Transform(node *ast.Document, _ text.Reader, pc parser.Context) {
+ _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
"bytes"
+
+ ctx := pc.Get(renderContextKey).(markdownContext)
+
+ switch v := n.(type) {
+ case *ast.Image:
+ link := v.Destination
+ if len(link) > 0 && !bytes.HasPrefix(link, []byte("http")) {
+ v.Destination = []byte(resolveLink(ctx.repo, ctx.ref, ctx.path, string(link)) + "?raw&pretty")
+ }
+
+ parent := n.Parent()
+ if _, ok := parent.(*ast.Link); !ok && parent != nil {
+ emoji "github.com/yuin/goldmark-emoji"
package html
+ emoji "github.com/yuin/goldmark-emoji"
+ wrapper.Destination = v.Destination
+ emoji "github.com/yuin/goldmark-emoji"
"bytes"
+ wrapper.SetAttributeString("target", []byte("_blank"))
+ img := ast.NewImage(ast.NewLink())
+ emoji "github.com/yuin/goldmark-emoji"
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
+ img.Title = v.Title
+ for _, attr := range v.Attributes() {
+ img.SetAttribute(attr.Name, attr.Value)
+ }
+ for child := v.FirstChild(); child != nil; {
+ highlighting "github.com/yuin/goldmark-highlighting/v2"
import (
+ img.AppendChild(img, child)
+ child = nextChild
+ }
+ wrapper.AppendChild(wrapper, img)
+ highlighting "github.com/yuin/goldmark-highlighting/v2"
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
+ parent.ReplaceChild(parent, n, wrapper)
+ v.SetNextSibling(next)
+ }
+ case *ast.Link:
+ link := v.Destination
+ if len(link) > 0 && !bytes.HasPrefix(link, []byte("http")) && link[0] != '#' && !bytes.HasPrefix(link, []byte("mailto")) {
+ v.Destination = []byte(resolveLink(ctx.repo, ctx.ref, ctx.path, string(link)))
+ }
+ }
+
+ "github.com/yuin/goldmark/extension"
import (
+ })
+}
+
+func postProcess(in string, ctx markdownContext, out io.Writer) error {
+ node, err := html.Parse(strings.NewReader("<html><body>" + in + "</body></html"))
+ if err != nil {
+ "github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark"
"bytes"
+package html
+ if node.Type == html.DocumentNode {
+ node = node.FirstChild
+ }
+
+ process(ctx, node)
+
+ renderNodes := make([]*html.Node, 0)
+ if node.Data == "html" {
+ node = node.FirstChild
+ for node != nil && node.Data != "body" {
+ node = node.NextSibling
+ }
+ }
+ if node != nil {
+ if node.Data == "body" {
+ "github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark"
+ for child != nil {
+ renderNodes = append(renderNodes, child)
+ child = child.NextSibling
+ }
+ } else {
+ renderNodes = append(renderNodes, node)
}
}
+ for _, node := range renderNodes {
+ if err := html.Render(out, node); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+func process(ctx markdownContext, node *html.Node) {
+ if node.Type == html.ElementNode && node.Data == "img" {
+ for i, attr := range node.Attr {
+ if attr.Key != "src" {
+ continue
+ }
+ if len(attr.Val) > 0 && !strings.HasPrefix(attr.Val, "http") && !strings.HasPrefix(attr.Val, "data:image/") {
+ attr.Val = resolveLink(ctx.repo, ctx.ref, ctx.path, attr.Val) + "?raw&pretty"
+ }
+ node.Attr[i] = attr
+ }
+ }
+ for n := node.FirstChild; n != nil; n = n.NextSibling {
+ process(ctx, n)
+ }
+}
+
+func resolveLink(repo, ref, path, link string) string {
+package html
return "", nil
+ if err != nil {
+ return ""
+ }
+ linkURL, err := url.Parse(link)
+ if err != nil {
+ return ""
+ }
+ return baseURL.ResolveReference(linkURL).String()
}
diff --git a/internal/html/repo_commit.templ b/internal/html/repo_commit.templ
index 7f07f145081601e442a16b27556e1ce276f54949..bb95a59fae886baa92f3de71e39949ff5b0abe7d 100644
--- a/internal/html/repo_commit.templ
+++ b/internal/html/repo_commit.templ
@@ -22,7 +22,20 @@ </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 {
+type RepoCommitContext struct{
+ BaseContext
+ { " " }
+ 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 commit">@templ.Raw(file.Patch)</div>
}
}
diff --git a/internal/html/repo_commit_templ.go b/internal/html/repo_commit_templ.go
index 8a48de28e800dc0136b43650eb499947f4eb50cb..49c39c56f407fc6bb805e9aa465764d74745604e 100644
--- a/internal/html/repo_commit_templ.go
+++ b/internal/html/repo_commit_templ.go
@@ -219,8 +219,16 @@ if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, file := range rcc.Commit.Files {
+type RepoCommitContext struct {
import "bytes"
-import "github.com/a-h/templ"
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(file.Action))
+ 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
}
@@ -228,14 +236,15 @@ var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(string(file.Action[0]))
if templ_7745c5c3_Err != nil {
// Code generated by templ - DO NOT EDIT.
-
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
// Code generated by templ - DO NOT EDIT.
package html
+
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -242,133 +252,153 @@ var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
if templ_7745c5c3_Err != nil {
// Code generated by templ - DO NOT EDIT.
-import "context"
+ templ_7745c5c3_Var4 := `tree`
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
// Code generated by templ - DO NOT EDIT.
-import "bytes"
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
// Code generated by templ - DO NOT EDIT.
-import "fmt"
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
// Code generated by templ - DO NOT EDIT.
+ var templ_7745c5c3_Var5 string
// Code generated by templ - DO NOT EDIT.
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
// Code generated by templ - DO NOT EDIT.
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 15, Col: 229}
+ BaseContext
import "bytes"
+ 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)))
+ BaseContext
import "context"
-import "bytes"
+ BaseContext
import "io"
-import "bytes"
+ BaseContext
import "bytes"
// Code generated by templ - DO NOT EDIT.
-// Code generated by templ - DO NOT EDIT.
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
-import "bytes"
+ BaseContext
import "context"
-import "bytes"
+ BaseContext
import "io"
-import "bytes"
+ BaseContext
import "bytes"
// Code generated by templ - DO NOT EDIT.
-// Code generated by templ - DO NOT EDIT.
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
// templ: version: v0.2.501
// Code generated by templ - DO NOT EDIT.
-// Code generated by templ - DO NOT EDIT.
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
package html
-import "bytes"
+ BaseContext
import "context"
// Code generated by templ - DO NOT EDIT.
-// Code generated by templ - DO NOT EDIT.
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
//lint:file-ignore SA4006 This context is only used if a nested component is present.
-import "bytes"
+ BaseContext
import "bytes"
// Code generated by templ - DO NOT EDIT.
-// Code generated by templ - DO NOT EDIT.
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
-import "bytes"
+ BaseContext
import "context"
-import "bytes"
+ BaseContext
import "io"
-import "bytes"
+ BaseContext
import "bytes"
// Code generated by templ - DO NOT EDIT.
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 15, Col: 427}
// Code generated by templ - DO NOT EDIT.
+package html
import "context"
- if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ BaseContext
import "bytes"
-import "io"
}
// Code generated by templ - DO NOT EDIT.
-// Code generated by templ - DO NOT EDIT.
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "io"
// Code generated by templ - DO NOT EDIT.
-// Code generated by templ - DO NOT EDIT.
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "bytes"
-import "bytes"
+ templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(" -> ")
+ BaseContext
import "context"
// Code generated by templ - DO NOT EDIT.
- templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
+ templ_7745c5c3_Var10 := `patch`
-import "bytes"
+ BaseContext
import "bytes"
// Code generated by templ - DO NOT EDIT.
+import "github.com/a-h/templ"
// Code generated by templ - DO NOT EDIT.
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
- if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ BaseContext
import "bytes"
-import "io"
}
// Code generated by templ - DO NOT EDIT.
-import "bytes"
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div class=\"text-text whitespace-pre mt-5\">")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"underline decoration-text/50 decoration-dashed hover:decoration-solid\" href=\"")
-import "bytes"
+ BaseContext
import "context"
-import "bytes"
+ BaseContext
import "io"
-import "bytes"
+ BaseContext
import "bytes"
// Code generated by templ - DO NOT EDIT.
- templ_7745c5c3_Buffer = templ.GetBuffer()
+ var templ_7745c5c3_Var11 string
// Code generated by templ - DO NOT EDIT.
- defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(rcc.Commit.Message)
-import "bytes"
+ BaseContext
import "context"
-import "bytes"
+ BaseContext
import "io"
-import "bytes"
+ BaseContext
import "bytes"
// Code generated by templ - DO NOT EDIT.
-// Code generated by templ - DO NOT EDIT.
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
-import "bytes"
+ BaseContext
import "context"
-import "bytes"
+ BaseContext
import "io"
-import "bytes"
+ BaseContext
import "bytes"
// Code generated by templ - DO NOT EDIT.
- }
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `repo_commit.templ`, Line: 16, Col: 65}
// Code generated by templ - DO NOT EDIT.
- ctx = templ.InitializeContext(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
-import "bytes"
+ BaseContext
import "context"
// Code generated by templ - DO NOT EDIT.
-
import "github.com/a-h/templ"
+import "io"
+ BaseContext
import "bytes"
+ Commit git.Commit
import "bytes"
// Code generated by templ - DO NOT EDIT.
-
+package html
import "context"
+ return templ_7745c5c3_Err
+ BaseContext
import "bytes"
+ RepoHeaderComponentContext
import "context"
-import "bytes"
+ if templ_7745c5c3_Err != nil {
+ BaseContext
import "io"
+ }
}
// Code generated by templ - DO NOT EDIT.
- templ_7745c5c3_Var1 = templ.NopComponent
+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(rcc.Commit.Author)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}