mirror of https://git.jolheiser.com/ugit.git
parent
5f408c1fb1
commit
22cdff623a
|
@ -51,6 +51,7 @@ func PathExists(path string) (bool, error) {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tree returns the git tree at a given ref/rev
|
||||||
func (r Repo) Tree(ref string) (*object.Tree, error) {
|
func (r Repo) Tree(ref string) (*object.Tree, error) {
|
||||||
g, err := r.Git()
|
g, err := r.Git()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -70,6 +71,7 @@ func (r Repo) Tree(ref string) (*object.Tree, error) {
|
||||||
return c.Tree()
|
return c.Tree()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FileInfo is the information for a file in a tree
|
||||||
type FileInfo struct {
|
type FileInfo struct {
|
||||||
Path string
|
Path string
|
||||||
IsDir bool
|
IsDir bool
|
||||||
|
@ -77,10 +79,13 @@ type FileInfo struct {
|
||||||
Size string
|
Size string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the last part of the FileInfo.Path
|
||||||
func (f FileInfo) Name() string {
|
func (f FileInfo) Name() string {
|
||||||
return filepath.Base(f.Path)
|
return filepath.Base(f.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dir returns the given dirpath in the given ref as a slice of FileInfo
|
||||||
|
// Sorted alphabetically, dirs first
|
||||||
func (r Repo) Dir(ref, path string) ([]FileInfo, error) {
|
func (r Repo) Dir(ref, path string) ([]FileInfo, error) {
|
||||||
t, err := r.Tree(ref)
|
t, err := r.Tree(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -119,6 +124,7 @@ func (r Repo) Dir(ref, path string) ([]FileInfo, error) {
|
||||||
return fis, nil
|
return fis, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
func (r Repo) FileContent(ref, file string) (string, error) {
|
||||||
t, err := r.Tree(ref)
|
t, err := r.Tree(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -8,11 +8,13 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RepoMeta is the meta information a Repo can have
|
||||||
type RepoMeta struct {
|
type RepoMeta struct {
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Private bool `json:"private"`
|
Private bool `json:"private"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update updates meta given another RepoMeta
|
||||||
func (m *RepoMeta) Update(meta RepoMeta) error {
|
func (m *RepoMeta) Update(meta RepoMeta) error {
|
||||||
data, err := json.Marshal(meta)
|
data, err := json.Marshal(meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -25,13 +27,14 @@ func (r Repo) metaPath() string {
|
||||||
return filepath.Join(r.path, "ugit.json")
|
return filepath.Join(r.path, "ugit.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveMeta saves the meta info of a Repo
|
||||||
func (r Repo) SaveMeta() error {
|
func (r Repo) SaveMeta() error {
|
||||||
// Compatibility with gitweb, because why not
|
// Compatibility with gitweb, because why not
|
||||||
// Ignoring the error because it's not technically detrimental to ugit
|
// Ignoring the error because it's not technically detrimental to ugit
|
||||||
desc, err := os.Create(filepath.Join(r.path, "description"))
|
desc, err := os.Create(filepath.Join(r.path, "description"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer desc.Close()
|
defer desc.Close()
|
||||||
desc.WriteString(r.Meta.Description)
|
_, _ = desc.WriteString(r.Meta.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := os.Create(r.metaPath())
|
fi, err := os.Create(r.metaPath())
|
||||||
|
|
|
@ -20,16 +20,19 @@ import (
|
||||||
"github.com/go-git/go-git/v5/utils/ioutil"
|
"github.com/go-git/go-git/v5/utils/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ReadWriteContexter is the interface required to operate on git protocols
|
||||||
type ReadWriteContexter interface {
|
type ReadWriteContexter interface {
|
||||||
io.ReadWriteCloser
|
io.ReadWriteCloser
|
||||||
Context() context.Context
|
Context() context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Protocol handles the endpoint and server of the git protocols
|
||||||
type Protocol struct {
|
type Protocol struct {
|
||||||
endpoint *transport.Endpoint
|
endpoint *transport.Endpoint
|
||||||
server transport.Transport
|
server transport.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewProtocol constructs a Protocol for a given repo
|
||||||
func NewProtocol(repoPath string) (Protocol, error) {
|
func NewProtocol(repoPath string) (Protocol, error) {
|
||||||
endpoint, err := transport.NewEndpoint("/")
|
endpoint, err := transport.NewEndpoint("/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -44,6 +47,7 @@ func NewProtocol(repoPath string) (Protocol, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPInfoRefs handles the inforef part of the HTTP protocol
|
||||||
func (p Protocol) HTTPInfoRefs(rwc ReadWriteContexter) error {
|
func (p Protocol) HTTPInfoRefs(rwc ReadWriteContexter) error {
|
||||||
session, err := p.server.NewUploadPackSession(p.endpoint, nil)
|
session, err := p.server.NewUploadPackSession(p.endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -73,10 +77,12 @@ func (p Protocol) infoRefs(rwc ReadWriteContexter, session transport.UploadPackS
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPUploadPack handles the upload-pack process for HTTP
|
||||||
func (p Protocol) HTTPUploadPack(rwc ReadWriteContexter) error {
|
func (p Protocol) HTTPUploadPack(rwc ReadWriteContexter) error {
|
||||||
return p.uploadPack(rwc, false)
|
return p.uploadPack(rwc, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSHUploadPack handles the upload-pack process for SSH
|
||||||
func (p Protocol) SSHUploadPack(rwc ReadWriteContexter) error {
|
func (p Protocol) SSHUploadPack(rwc ReadWriteContexter) error {
|
||||||
return p.uploadPack(rwc, true)
|
return p.uploadPack(rwc, true)
|
||||||
}
|
}
|
||||||
|
@ -112,6 +118,7 @@ func (p Protocol) uploadPack(rwc ReadWriteContexter, ssh bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSHReceivePack handles the receive-pack process for SSH
|
||||||
func (p Protocol) SSHReceivePack(rwc ReadWriteContexter, repo *Repo) error {
|
func (p Protocol) SSHReceivePack(rwc ReadWriteContexter, repo *Repo) error {
|
||||||
buf := bufio.NewReader(rwc)
|
buf := bufio.NewReader(rwc)
|
||||||
|
|
||||||
|
@ -213,6 +220,7 @@ func handlePushOptions(repo *Repo, opts []*packp.Option) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateServerInfo handles updating server info for the git repo
|
||||||
func UpdateServerInfo(repo string) error {
|
func UpdateServerInfo(repo string) error {
|
||||||
r, err := git.PlainOpen(repo)
|
r, err := git.PlainOpen(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -15,15 +15,18 @@ import (
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Repo is a git repository
|
||||||
type Repo struct {
|
type Repo struct {
|
||||||
path string
|
path string
|
||||||
Meta RepoMeta
|
Meta RepoMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the human-friendly name, the dir name without the .git suffix
|
||||||
func (r Repo) Name() string {
|
func (r Repo) Name() string {
|
||||||
return strings.TrimSuffix(filepath.Base(r.path), ".git")
|
return strings.TrimSuffix(filepath.Base(r.path), ".git")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRepo constructs a Repo given a dir and name
|
||||||
func NewRepo(dir, name string) (*Repo, error) {
|
func NewRepo(dir, name string) (*Repo, error) {
|
||||||
if !strings.HasSuffix(name, ".git") {
|
if !strings.HasSuffix(name, ".git") {
|
||||||
name += ".git"
|
name += ".git"
|
||||||
|
@ -98,6 +101,7 @@ type Commit struct {
|
||||||
Author string
|
Author string
|
||||||
Email string
|
Email string
|
||||||
When time.Time
|
When time.Time
|
||||||
|
|
||||||
// Extra
|
// Extra
|
||||||
Stats CommitStats
|
Stats CommitStats
|
||||||
Patch string
|
Patch string
|
||||||
|
@ -125,19 +129,22 @@ type CommitFileEntry struct {
|
||||||
Commit string
|
Commit string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Short returns the first eight characters of the SHA
|
||||||
func (c Commit) Short() string {
|
func (c Commit) Short() string {
|
||||||
return c.SHA[:8]
|
return c.SHA[:8]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Summary returns the first line of the commit, suitable for a <summary>
|
||||||
func (c Commit) Summary() string {
|
func (c Commit) Summary() string {
|
||||||
return strings.Split(c.Message, "\n")[0]
|
return strings.Split(c.Message, "\n")[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Details returns all lines *after* the first, suitable for <details>
|
||||||
func (c Commit) Details() string {
|
func (c Commit) Details() string {
|
||||||
return strings.Join(strings.Split(c.Message, "\n")[1:], "\n")
|
return strings.Join(strings.Split(c.Message, "\n")[1:], "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit gets a specific commit by SHA
|
// Commit gets a specific commit by SHA, including all commit information
|
||||||
func (r Repo) Commit(sha string) (Commit, error) {
|
func (r Repo) Commit(sha string) (Commit, error) {
|
||||||
repo, err := r.Git()
|
repo, err := r.Git()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -147,7 +154,7 @@ func (r Repo) Commit(sha string) (Commit, error) {
|
||||||
return commit(repo, sha, true)
|
return commit(repo, sha, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastCommit returns the last commit of the repo
|
// LastCommit returns the last commit of the repo without any extra information
|
||||||
func (r Repo) LastCommit() (Commit, error) {
|
func (r Repo) LastCommit() (Commit, error) {
|
||||||
repo, err := r.Git()
|
repo, err := r.Git()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -313,15 +320,8 @@ func (r Repo) Tags() ([]Tag, error) {
|
||||||
var tags []Tag
|
var tags []Tag
|
||||||
if err := tgs.ForEach(func(tag *plumbing.Reference) error {
|
if err := tgs.ForEach(func(tag *plumbing.Reference) error {
|
||||||
obj, err := repo.TagObject(tag.Hash())
|
obj, err := repo.TagObject(tag.Hash())
|
||||||
switch err {
|
switch {
|
||||||
case nil:
|
case errors.Is(err, plumbing.ErrObjectNotFound):
|
||||||
tags = append(tags, Tag{
|
|
||||||
Name: obj.Name,
|
|
||||||
Annotation: obj.Message,
|
|
||||||
Signature: obj.PGPSignature,
|
|
||||||
When: obj.Tagger.When,
|
|
||||||
})
|
|
||||||
case plumbing.ErrObjectNotFound:
|
|
||||||
commit, err := repo.CommitObject(tag.Hash())
|
commit, err := repo.CommitObject(tag.Hash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -332,6 +332,13 @@ func (r Repo) Tags() ([]Tag, error) {
|
||||||
Signature: commit.PGPSignature,
|
Signature: commit.PGPSignature,
|
||||||
When: commit.Author.When,
|
When: commit.Author.When,
|
||||||
})
|
})
|
||||||
|
case err == nil:
|
||||||
|
tags = append(tags, Tag{
|
||||||
|
Name: obj.Name,
|
||||||
|
Annotation: obj.Message,
|
||||||
|
Signature: obj.PGPSignature,
|
||||||
|
When: obj.Tagger.When,
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,11 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go.jolheiser.com/ugit/internal/html/markup"
|
||||||
"go/format"
|
"go/format"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"go.jolheiser.com/ugit/internal/html"
|
|
||||||
|
|
||||||
"github.com/alecthomas/chroma/v2/styles"
|
"github.com/alecthomas/chroma/v2/styles"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,6 +32,7 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate tailwind code from templ templates and combine with other misc CSS
|
||||||
func tailwind() error {
|
func tailwind() error {
|
||||||
fmt.Println("generating tailwind...")
|
fmt.Println("generating tailwind...")
|
||||||
|
|
||||||
|
@ -48,13 +48,13 @@ func tailwind() error {
|
||||||
fmt.Println("generating chroma styles...")
|
fmt.Println("generating chroma styles...")
|
||||||
|
|
||||||
latte := styles.Get("catppuccin-latte")
|
latte := styles.Get("catppuccin-latte")
|
||||||
if err := html.Formatter.WriteCSS(tmp, latte); err != nil {
|
if err := markup.Formatter.WriteCSS(tmp, latte); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp.WriteString("@media (prefers-color-scheme: dark) {")
|
tmp.WriteString("@media (prefers-color-scheme: dark) {")
|
||||||
mocha := styles.Get("catppuccin-mocha")
|
mocha := styles.Get("catppuccin-mocha")
|
||||||
if err := html.Formatter.WriteCSS(tmp, mocha); err != nil {
|
if err := markup.Formatter.WriteCSS(tmp, mocha); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tmp.WriteString("}")
|
tmp.WriteString("}")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package html
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
@ -10,6 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// Formatter is the default formatter
|
||||||
Formatter = html.New(
|
Formatter = html.New(
|
||||||
html.WithLineNumbers(true),
|
html.WithLineNumbers(true),
|
||||||
html.WithLinkableLineNumbers(true, "L"),
|
html.WithLinkableLineNumbers(true, "L"),
|
||||||
|
@ -19,6 +20,7 @@ var (
|
||||||
basicFormatter = html.New(
|
basicFormatter = html.New(
|
||||||
html.WithClasses(true),
|
html.WithClasses(true),
|
||||||
)
|
)
|
||||||
|
// Code is the entrypoint for formatting
|
||||||
Code = code{}
|
Code = code{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,6 +46,7 @@ func (c code) setup(source []byte, fileName string) (chroma.Iterator, *chroma.St
|
||||||
return iter, style, nil
|
return iter, style, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Basic formats code without any extras
|
||||||
func (c code) Basic(source []byte, fileName string, writer io.Writer) error {
|
func (c code) Basic(source []byte, fileName string, writer io.Writer) error {
|
||||||
iter, style, err := c.setup(source, fileName)
|
iter, style, err := c.setup(source, fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -52,6 +55,7 @@ func (c code) Basic(source []byte, fileName string, writer io.Writer) error {
|
||||||
return basicFormatter.Format(writer, style, iter)
|
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 (c code) Convert(source []byte, fileName string, writer io.Writer) error {
|
||||||
iter, style, err := c.setup(source, fileName)
|
iter, style, err := c.setup(source, fileName)
|
||||||
if err != nil {
|
if err != nil {
|
|
@ -1,4 +1,4 @@
|
||||||
package html
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -48,6 +48,7 @@ var markdown = goldmark.New(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Readme transforms a readme, potentially from markdown, into HTML
|
||||||
func Readme(repo *git.Repo, ref, path string) (string, error) {
|
func Readme(repo *git.Repo, ref, path string) (string, error) {
|
||||||
var readme string
|
var readme string
|
||||||
var err error
|
var err error
|
||||||
|
@ -98,6 +99,9 @@ type markdownContext struct {
|
||||||
|
|
||||||
type astTransformer struct{}
|
type astTransformer struct{}
|
||||||
|
|
||||||
|
// Transform does two main things
|
||||||
|
// 1. Changes images to work relative to the source and wraps them in links
|
||||||
|
// 2. Changes links to work relative to the source
|
||||||
func (a astTransformer) Transform(node *ast.Document, _ text.Reader, pc parser.Context) {
|
func (a astTransformer) Transform(node *ast.Document, _ text.Reader, pc parser.Context) {
|
||||||
_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
|
_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if !entering {
|
if !entering {
|
|
@ -20,14 +20,17 @@ func (h httpError) Unwrap() error {
|
||||||
return h.err
|
return h.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error returns a generic 500 error
|
||||||
func Error(err error) httpError {
|
func Error(err error) httpError {
|
||||||
return Status(err, http.StatusInternalServerError)
|
return Status(err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Status returns a set status with the error
|
||||||
func Status(err error, status int) httpError {
|
func Status(err error, status int) httpError {
|
||||||
return httpError{err: err, status: status}
|
return httpError{err: err, status: status}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handler transforms an http handler + error into a stdlib handler
|
||||||
func Handler(fn func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
|
func Handler(fn func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := fn(w, r); err != nil {
|
if err := fn(w, r); err != nil {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package http
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"go.jolheiser.com/ugit/internal/html/markup"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -46,7 +47,7 @@ func (rh repoHandler) repoTree(ref, path string) http.HandlerFunc {
|
||||||
return httperr.Error(err)
|
return httperr.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
readmeContent, err := html.Readme(repo, ref, path)
|
readmeContent, err := markup.Readme(repo, ref, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperr.Error(err)
|
return httperr.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -92,7 +93,7 @@ func (rh repoHandler) repoFile(w http.ResponseWriter, r *http.Request, repo *git
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := html.Code.Convert([]byte(content), filepath.Base(path), &buf); err != nil {
|
if err := markup.Code.Convert([]byte(content), filepath.Base(path), &buf); err != nil {
|
||||||
return httperr.Error(err)
|
return httperr.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +196,7 @@ func (rh repoHandler) repoCommit(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
|
||||||
for idx, p := range commit.Files {
|
for idx, p := range commit.Files {
|
||||||
var patch bytes.Buffer
|
var patch bytes.Buffer
|
||||||
if err := html.Code.Basic([]byte(p.Patch), "commit.patch", &patch); err != nil {
|
if err := markup.Code.Basic([]byte(p.Patch), "commit.patch", &patch); err != nil {
|
||||||
return httperr.Error(err)
|
return httperr.Error(err)
|
||||||
}
|
}
|
||||||
commit.Files[idx].Patch = patch.String()
|
commit.Files[idx].Patch = patch.String()
|
||||||
|
|
Loading…
Reference in New Issue