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