Home

ugit @main - refs - log -
-
https://git.jolheiser.com/ugit.git
The code powering this h*ckin' site
tree log patch
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-----
jolheiser <john.olheiser@gmail.com>
11 months ago
6 changed files, 252 additions(+), 54 deletions(-)
M README.md -> README.md
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
M flake.nix -> flake.nix
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";
M go.mod -> go.mod
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
M internal/html/markdown.go -> internal/html/markdown.go
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()
 }
M internal/html/repo_commit.templ -> internal/html/repo_commit.templ
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>
 		}
 	}
M internal/html/repo_commit_templ.go -> internal/html/repo_commit_templ.go
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
 				}