diff --git a/internal/git/git.go b/internal/git/git.go index 141ced5..c3649f9 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -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 { diff --git a/internal/git/meta.go b/internal/git/meta.go index 04f7f1b..9665cf9 100644 --- a/internal/git/meta.go +++ b/internal/git/meta.go @@ -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()) diff --git a/internal/git/protocol.go b/internal/git/protocol.go index 60b927d..2da2417 100644 --- a/internal/git/protocol.go +++ b/internal/git/protocol.go @@ -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 { diff --git a/internal/git/repo.go b/internal/git/repo.go index 67af17c..3272919 100644 --- a/internal/git/repo.go +++ b/internal/git/repo.go @@ -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 func (c Commit) Summary() string { return strings.Split(c.Message, "\n")[0] } +// Details returns all lines *after* the first, suitable for
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 } diff --git a/internal/html/generate.go b/internal/html/generate.go index b4c1a44..678f3a3 100644 --- a/internal/html/generate.go +++ b/internal/html/generate.go @@ -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("}") diff --git a/internal/html/chroma.go b/internal/html/markup/chroma.go similarity index 87% rename from internal/html/chroma.go rename to internal/html/markup/chroma.go index a551028..044303f 100644 --- a/internal/html/chroma.go +++ b/internal/html/markup/chroma.go @@ -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 { diff --git a/internal/html/markdown.go b/internal/html/markup/markdown.go similarity index 95% rename from internal/html/markdown.go rename to internal/html/markup/markdown.go index c4c8d5f..787fe1b 100644 --- a/internal/html/markdown.go +++ b/internal/html/markup/markdown.go @@ -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 { diff --git a/internal/http/httperr/httperr.go b/internal/http/httperr/httperr.go index b6bcd1f..de1dfa4 100644 --- a/internal/http/httperr/httperr.go +++ b/internal/http/httperr/httperr.go @@ -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 { diff --git a/internal/http/repo.go b/internal/http/repo.go index f1713d7..bc0196e 100644 --- a/internal/http/repo.go +++ b/internal/http/repo.go @@ -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()