add results in binary encoding for events endpoint
This commit is contained in:
52
cmd/legit/.github/workflows/docker.yaml
vendored
52
cmd/legit/.github/workflows/docker.yaml
vendored
@@ -1,52 +0,0 @@
|
||||
name: build and push docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
tags: ["*"]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Copy Dockerfile to .
|
||||
run: cp ./contrib/Dockerfile ./Dockerfile
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
push: true
|
||||
build-args: |
|
||||
BUILDKIT_INLINE_CACHE=1
|
||||
@@ -1,20 +0,0 @@
|
||||
repo:
|
||||
scanPath: /var/www/git
|
||||
readme:
|
||||
- readme
|
||||
- README
|
||||
- readme.md
|
||||
- README.md
|
||||
mainBranch:
|
||||
- master
|
||||
- main
|
||||
dirs:
|
||||
templates: ./templates
|
||||
static: ./static
|
||||
meta:
|
||||
title: icy does git
|
||||
description: come get your free software
|
||||
server:
|
||||
name: git.icyphox.sh
|
||||
host: 0.0.0.0
|
||||
port: 5555
|
||||
@@ -1,57 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Repo struct {
|
||||
ScanPath string `yaml:"scanPath"`
|
||||
Readme []string `yaml:"readme"`
|
||||
MainBranch []string `yaml:"mainBranch"`
|
||||
Ignore []string `yaml:"ignore,omitempty"`
|
||||
Unlisted []string `yaml:"unlisted,omitempty"`
|
||||
} `yaml:"repo"`
|
||||
Dirs struct {
|
||||
Templates string `yaml:"templates"`
|
||||
Static string `yaml:"static"`
|
||||
} `yaml:"dirs"`
|
||||
Meta struct {
|
||||
Title string `yaml:"title"`
|
||||
Description string `yaml:"description"`
|
||||
SyntaxHighlight string `yaml:"syntaxHighlight"`
|
||||
} `yaml:"meta"`
|
||||
Server struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
} `yaml:"server"`
|
||||
}
|
||||
|
||||
func Read(f string) (*Config, error) {
|
||||
b, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading config: %w", err)
|
||||
}
|
||||
|
||||
c := Config{}
|
||||
if err := yaml.Unmarshal(b, &c); err != nil {
|
||||
return nil, fmt.Errorf("parsing config: %w", err)
|
||||
}
|
||||
|
||||
if c.Repo.ScanPath, err = filepath.Abs(c.Repo.ScanPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.Dirs.Templates, err = filepath.Abs(c.Dirs.Templates); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.Dirs.Static, err = filepath.Abs(c.Dirs.Static); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
FROM golang:1.22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
RUN go mod download
|
||||
RUN go mod verify
|
||||
|
||||
RUN go build -o legit
|
||||
|
||||
FROM scratch AS build-release-stage
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY static ./static
|
||||
COPY templates ./templates
|
||||
COPY config.yaml ./
|
||||
COPY --from=builder /app/legit ./
|
||||
|
||||
EXPOSE 5555
|
||||
|
||||
CMD ["./legit"]
|
||||
@@ -1,14 +0,0 @@
|
||||
services:
|
||||
legit:
|
||||
container_name: legit
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: contrib/Dockerfile
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5555:5555"
|
||||
volumes:
|
||||
- /var/www/git:/var/www/git
|
||||
- ../config.yaml:/app/config.yaml
|
||||
- ../static:/app/static
|
||||
- ../templates:/app/templates
|
||||
@@ -1,17 +0,0 @@
|
||||
[Unit]
|
||||
Description=legit Server
|
||||
After=network-online.target
|
||||
Requires=network-online.target
|
||||
|
||||
[Service]
|
||||
User=git
|
||||
Group=git
|
||||
ExecStart=/usr/bin/legit -config /etc/legit/config.yaml
|
||||
ProtectSystem=strict
|
||||
ProtectHome=strict
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
26
cmd/legit/flake.lock
generated
26
cmd/legit/flake.lock
generated
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1718558927,
|
||||
"narHash": "sha256-PRqvkPqX5luuZ0WcUbz2zATGp4IzybDU0K33MxO9Sd0=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f82fe275d98c521c051af4892cd8b3406cee67a3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
{
|
||||
description = "web frontend for git";
|
||||
|
||||
inputs.nixpkgs.url = "github:nixos/nixpkgs";
|
||||
|
||||
outputs =
|
||||
{ self
|
||||
, nixpkgs
|
||||
,
|
||||
}:
|
||||
let
|
||||
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
|
||||
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (system:
|
||||
let
|
||||
pkgs = nixpkgsFor.${system};
|
||||
legit = self.packages.${system}.legit;
|
||||
files = pkgs.lib.fileset.toSource {
|
||||
root = ./.;
|
||||
fileset = pkgs.lib.fileset.unions [
|
||||
./config.yaml
|
||||
./static
|
||||
./templates
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
legit = pkgs.buildGoModule {
|
||||
name = "legit";
|
||||
rev = "master";
|
||||
src = ./.;
|
||||
|
||||
vendorHash = "sha256-ynv0pBdVPIhTz7RvCwVWr0vUWwfw+PEjFXs9PdQMqm8=";
|
||||
};
|
||||
docker = pkgs.dockerTools.buildLayeredImage {
|
||||
name = "sini:5000/legit";
|
||||
tag = "latest";
|
||||
contents = [ files legit pkgs.git ];
|
||||
config = {
|
||||
Entrypoint = [ "${legit}/bin/legit" ];
|
||||
ExposedPorts = { "5555/tcp" = { }; };
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
defaultPackage = forAllSystems (system: self.packages.${system}.legit);
|
||||
devShells = forAllSystems (system:
|
||||
let
|
||||
pkgs = nixpkgsFor.${system};
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
go
|
||||
];
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/bluekeyes/go-gitdiff/gitdiff"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
type TextFragment struct {
|
||||
Header string
|
||||
Lines []gitdiff.Line
|
||||
}
|
||||
|
||||
type Diff struct {
|
||||
Name struct {
|
||||
Old string
|
||||
New string
|
||||
}
|
||||
TextFragments []TextFragment
|
||||
IsBinary bool
|
||||
IsNew bool
|
||||
IsDelete bool
|
||||
}
|
||||
|
||||
// A nicer git diff representation.
|
||||
type NiceDiff struct {
|
||||
Commit struct {
|
||||
Message string
|
||||
Author object.Signature
|
||||
This string
|
||||
Parent string
|
||||
}
|
||||
Stat struct {
|
||||
FilesChanged int
|
||||
Insertions int
|
||||
Deletions int
|
||||
}
|
||||
Diff []Diff
|
||||
}
|
||||
|
||||
func (g *GitRepo) Diff() (*NiceDiff, error) {
|
||||
c, err := g.r.CommitObject(g.h)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("commit object: %w", err)
|
||||
}
|
||||
|
||||
patch := &object.Patch{}
|
||||
commitTree, err := c.Tree()
|
||||
parent := &object.Commit{}
|
||||
if err == nil {
|
||||
parentTree := &object.Tree{}
|
||||
if c.NumParents() != 0 {
|
||||
parent, err = c.Parents().Next()
|
||||
if err == nil {
|
||||
parentTree, err = parent.Tree()
|
||||
if err == nil {
|
||||
patch, err = parentTree.Patch(commitTree)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("patch: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
patch, err = parentTree.Patch(commitTree)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("patch: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diffs, _, err := gitdiff.Parse(strings.NewReader(patch.String()))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
nd := NiceDiff{}
|
||||
nd.Commit.This = c.Hash.String()
|
||||
|
||||
if parent.Hash.IsZero() {
|
||||
nd.Commit.Parent = ""
|
||||
} else {
|
||||
nd.Commit.Parent = parent.Hash.String()
|
||||
}
|
||||
nd.Commit.Author = c.Author
|
||||
nd.Commit.Message = c.Message
|
||||
|
||||
for _, d := range diffs {
|
||||
ndiff := Diff{}
|
||||
ndiff.Name.New = d.NewName
|
||||
ndiff.Name.Old = d.OldName
|
||||
ndiff.IsBinary = d.IsBinary
|
||||
ndiff.IsNew = d.IsNew
|
||||
ndiff.IsDelete = d.IsDelete
|
||||
|
||||
for _, tf := range d.TextFragments {
|
||||
ndiff.TextFragments = append(ndiff.TextFragments, TextFragment{
|
||||
Header: tf.Header(),
|
||||
Lines: tf.Lines,
|
||||
})
|
||||
for _, l := range tf.Lines {
|
||||
switch l.Op {
|
||||
case gitdiff.OpAdd:
|
||||
nd.Stat.Insertions += 1
|
||||
case gitdiff.OpDelete:
|
||||
nd.Stat.Deletions += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nd.Diff = append(nd.Diff, ndiff)
|
||||
}
|
||||
|
||||
nd.Stat.FilesChanged = len(diffs)
|
||||
|
||||
return &nd, nil
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"path"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
type GitRepo struct {
|
||||
r *git.Repository
|
||||
h plumbing.Hash
|
||||
}
|
||||
|
||||
type TagList struct {
|
||||
refs []*TagReference
|
||||
r *git.Repository
|
||||
}
|
||||
|
||||
// TagReference is used to list both tag and non-annotated tags.
|
||||
// Non-annotated tags should only contains a reference.
|
||||
// Annotated tags should contain its reference and its tag information.
|
||||
type TagReference struct {
|
||||
ref *plumbing.Reference
|
||||
tag *object.Tag
|
||||
}
|
||||
|
||||
// infoWrapper wraps the property of a TreeEntry so it can export fs.FileInfo
|
||||
// to tar WriteHeader
|
||||
type infoWrapper struct {
|
||||
name string
|
||||
size int64
|
||||
mode fs.FileMode
|
||||
modTime time.Time
|
||||
isDir bool
|
||||
}
|
||||
|
||||
func (self *TagList) Len() int {
|
||||
return len(self.refs)
|
||||
}
|
||||
|
||||
func (self *TagList) Swap(i, j int) {
|
||||
self.refs[i], self.refs[j] = self.refs[j], self.refs[i]
|
||||
}
|
||||
|
||||
// sorting tags in reverse chronological order
|
||||
func (self *TagList) Less(i, j int) bool {
|
||||
var dateI time.Time
|
||||
var dateJ time.Time
|
||||
|
||||
if self.refs[i].tag != nil {
|
||||
dateI = self.refs[i].tag.Tagger.When
|
||||
} else {
|
||||
c, err := self.r.CommitObject(self.refs[i].ref.Hash())
|
||||
if err != nil {
|
||||
dateI = time.Now()
|
||||
} else {
|
||||
dateI = c.Committer.When
|
||||
}
|
||||
}
|
||||
|
||||
if self.refs[j].tag != nil {
|
||||
dateJ = self.refs[j].tag.Tagger.When
|
||||
} else {
|
||||
c, err := self.r.CommitObject(self.refs[j].ref.Hash())
|
||||
if err != nil {
|
||||
dateJ = time.Now()
|
||||
} else {
|
||||
dateJ = c.Committer.When
|
||||
}
|
||||
}
|
||||
|
||||
return dateI.After(dateJ)
|
||||
}
|
||||
|
||||
func Open(path string, ref string) (*GitRepo, error) {
|
||||
var err error
|
||||
g := GitRepo{}
|
||||
g.r, err = git.PlainOpen(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening %s: %w", path, err)
|
||||
}
|
||||
|
||||
if ref == "" {
|
||||
head, err := g.r.Head()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting head of %s: %w", path, err)
|
||||
}
|
||||
g.h = head.Hash()
|
||||
} else {
|
||||
hash, err := g.r.ResolveRevision(plumbing.Revision(ref))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving rev %s for %s: %w", ref, path, err)
|
||||
}
|
||||
g.h = *hash
|
||||
}
|
||||
return &g, nil
|
||||
}
|
||||
|
||||
func (g *GitRepo) Commits() ([]*object.Commit, error) {
|
||||
ci, err := g.r.Log(&git.LogOptions{From: g.h})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("commits from ref: %w", err)
|
||||
}
|
||||
|
||||
commits := []*object.Commit{}
|
||||
ci.ForEach(func(c *object.Commit) error {
|
||||
commits = append(commits, c)
|
||||
return nil
|
||||
})
|
||||
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
func (g *GitRepo) LastCommit() (*object.Commit, error) {
|
||||
c, err := g.r.CommitObject(g.h)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("last commit: %w", err)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (g *GitRepo) FileContent(path string) (string, error) {
|
||||
c, err := g.r.CommitObject(g.h)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("commit object: %w", err)
|
||||
}
|
||||
|
||||
tree, err := c.Tree()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("file tree: %w", err)
|
||||
}
|
||||
|
||||
file, err := tree.File(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
isbin, _ := file.IsBinary()
|
||||
|
||||
if !isbin {
|
||||
return file.Contents()
|
||||
} else {
|
||||
return "Not displaying binary file", nil
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GitRepo) Tags() ([]*TagReference, error) {
|
||||
iter, err := g.r.Tags()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tag objects: %w", err)
|
||||
}
|
||||
|
||||
tags := make([]*TagReference, 0)
|
||||
|
||||
if err := iter.ForEach(func(ref *plumbing.Reference) error {
|
||||
obj, err := g.r.TagObject(ref.Hash())
|
||||
switch err {
|
||||
case nil:
|
||||
tags = append(tags, &TagReference{
|
||||
ref: ref,
|
||||
tag: obj,
|
||||
})
|
||||
case plumbing.ErrObjectNotFound:
|
||||
tags = append(tags, &TagReference{
|
||||
ref: ref,
|
||||
})
|
||||
default:
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tagList := &TagList{r: g.r, refs: tags}
|
||||
sort.Sort(tagList)
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func (g *GitRepo) Branches() ([]*plumbing.Reference, error) {
|
||||
bi, err := g.r.Branches()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("branchs: %w", err)
|
||||
}
|
||||
|
||||
branches := []*plumbing.Reference{}
|
||||
|
||||
_ = bi.ForEach(func(ref *plumbing.Reference) error {
|
||||
branches = append(branches, ref)
|
||||
return nil
|
||||
})
|
||||
|
||||
return branches, nil
|
||||
}
|
||||
|
||||
func (g *GitRepo) FindMainBranch(branches []string) (string, error) {
|
||||
for _, b := range branches {
|
||||
_, err := g.r.ResolveRevision(plumbing.Revision(b))
|
||||
if err == nil {
|
||||
return b, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("unable to find main branch")
|
||||
}
|
||||
|
||||
// WriteTar writes itself from a tree into a binary tar file format.
|
||||
// prefix is root folder to be appended.
|
||||
func (g *GitRepo) WriteTar(w io.Writer, prefix string) error {
|
||||
tw := tar.NewWriter(w)
|
||||
defer tw.Close()
|
||||
|
||||
c, err := g.r.CommitObject(g.h)
|
||||
if err != nil {
|
||||
return fmt.Errorf("commit object: %w", err)
|
||||
}
|
||||
|
||||
tree, err := c.Tree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
walker := object.NewTreeWalker(tree, true, nil)
|
||||
defer walker.Close()
|
||||
|
||||
name, entry, err := walker.Next()
|
||||
for ; err == nil; name, entry, err = walker.Next() {
|
||||
info, err := newInfoWrapper(name, prefix, &entry, tree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := tar.FileInfoHeader(info, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tw.WriteHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
file, err := tree.File(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reader, err := file.Blob.Reader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(tw, reader)
|
||||
if err != nil {
|
||||
reader.Close()
|
||||
return err
|
||||
}
|
||||
reader.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newInfoWrapper(
|
||||
name string,
|
||||
prefix string,
|
||||
entry *object.TreeEntry,
|
||||
tree *object.Tree,
|
||||
) (*infoWrapper, error) {
|
||||
var (
|
||||
size int64
|
||||
mode fs.FileMode
|
||||
isDir bool
|
||||
)
|
||||
|
||||
if entry.Mode.IsFile() {
|
||||
file, err := tree.TreeEntryFile(entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mode = fs.FileMode(file.Mode)
|
||||
|
||||
size, err = tree.Size(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
isDir = true
|
||||
mode = fs.ModeDir | fs.ModePerm
|
||||
}
|
||||
|
||||
fullname := path.Join(prefix, name)
|
||||
return &infoWrapper{
|
||||
name: fullname,
|
||||
size: size,
|
||||
mode: mode,
|
||||
modTime: time.Unix(0, 0),
|
||||
isDir: isDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *infoWrapper) Name() string {
|
||||
return i.name
|
||||
}
|
||||
|
||||
func (i *infoWrapper) Size() int64 {
|
||||
return i.size
|
||||
}
|
||||
|
||||
func (i *infoWrapper) Mode() fs.FileMode {
|
||||
return i.mode
|
||||
}
|
||||
|
||||
func (i *infoWrapper) ModTime() time.Time {
|
||||
return i.modTime
|
||||
}
|
||||
|
||||
func (i *infoWrapper) IsDir() bool {
|
||||
return i.isDir
|
||||
}
|
||||
|
||||
func (i *infoWrapper) Sys() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TagReference) Name() string {
|
||||
return t.ref.Name().Short()
|
||||
}
|
||||
|
||||
func (t *TagReference) Message() string {
|
||||
if t.tag != nil {
|
||||
return t.tag.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Mostly from charmbracelet/soft-serve and sosedoff/gitkit.
|
||||
|
||||
type ServiceCommand struct {
|
||||
Dir string
|
||||
Stdin io.Reader
|
||||
Stdout http.ResponseWriter
|
||||
}
|
||||
|
||||
func (c *ServiceCommand) InfoRefs() error {
|
||||
cmd := exec.Command("git", []string{
|
||||
"upload-pack",
|
||||
"--stateless-rpc",
|
||||
"--advertise-refs",
|
||||
".",
|
||||
}...)
|
||||
|
||||
cmd.Dir = c.Dir
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
stdoutPipe, _ := cmd.StdoutPipe()
|
||||
cmd.Stderr = cmd.Stdout
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Printf("git: failed to start git-upload-pack (info/refs): %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := packLine(c.Stdout, "# service=git-upload-pack\n"); err != nil {
|
||||
log.Printf("git: failed to write pack line: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := packFlush(c.Stdout); err != nil {
|
||||
log.Printf("git: failed to flush pack: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
if _, err := io.Copy(&buf, stdoutPipe); err != nil {
|
||||
log.Printf("git: failed to copy stdout to tmp buffer: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
out := strings.Builder{}
|
||||
_, _ = io.Copy(&out, &buf)
|
||||
log.Printf("git: failed to run git-upload-pack; err: %s; output: %s", err, out.String())
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(c.Stdout, &buf); err != nil {
|
||||
log.Printf("git: failed to copy stdout: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ServiceCommand) UploadPack() error {
|
||||
cmd := exec.Command("git", []string{
|
||||
"-c", "uploadpack.allowFilter=true",
|
||||
"upload-pack",
|
||||
"--stateless-rpc",
|
||||
".",
|
||||
}...)
|
||||
cmd.Dir = c.Dir
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
|
||||
stdoutPipe, _ := cmd.StdoutPipe()
|
||||
cmd.Stderr = cmd.Stdout
|
||||
defer stdoutPipe.Close()
|
||||
|
||||
stdinPipe, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stdinPipe.Close()
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Printf("git: failed to start git-upload-pack: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(stdinPipe, c.Stdin); err != nil {
|
||||
log.Printf("git: failed to copy stdin: %s", err)
|
||||
return err
|
||||
}
|
||||
stdinPipe.Close()
|
||||
|
||||
if _, err := io.Copy(newWriteFlusher(c.Stdout), stdoutPipe); err != nil {
|
||||
log.Printf("git: failed to copy stdout: %s", err)
|
||||
return err
|
||||
}
|
||||
if err := cmd.Wait(); err != nil {
|
||||
log.Printf("git: failed to wait for git-upload-pack: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func packLine(w io.Writer, s string) error {
|
||||
_, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s)
|
||||
return err
|
||||
}
|
||||
|
||||
func packFlush(w io.Writer) error {
|
||||
_, err := fmt.Fprint(w, "0000")
|
||||
return err
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func newWriteFlusher(w http.ResponseWriter) io.Writer {
|
||||
return writeFlusher{w.(interface {
|
||||
io.Writer
|
||||
http.Flusher
|
||||
})}
|
||||
}
|
||||
|
||||
type writeFlusher struct {
|
||||
wf interface {
|
||||
io.Writer
|
||||
http.Flusher
|
||||
}
|
||||
}
|
||||
|
||||
func (w writeFlusher) Write(p []byte) (int, error) {
|
||||
defer w.wf.Flush()
|
||||
return w.wf.Write(p)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
func (g *GitRepo) FileTree(path string) ([]NiceTree, error) {
|
||||
c, err := g.r.CommitObject(g.h)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("commit object: %w", err)
|
||||
}
|
||||
|
||||
files := []NiceTree{}
|
||||
tree, err := c.Tree()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("file tree: %w", err)
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
files = makeNiceTree(tree)
|
||||
} else {
|
||||
o, err := tree.FindEntry(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !o.Mode.IsFile() {
|
||||
subtree, err := tree.Tree(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files = makeNiceTree(subtree)
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// A nicer git tree representation.
|
||||
type NiceTree struct {
|
||||
Name string
|
||||
Mode string
|
||||
Size int64
|
||||
IsFile bool
|
||||
IsSubtree bool
|
||||
}
|
||||
|
||||
func makeNiceTree(t *object.Tree) []NiceTree {
|
||||
nts := []NiceTree{}
|
||||
|
||||
for _, e := range t.Entries {
|
||||
mode, _ := e.Mode.ToOSFileMode()
|
||||
sz, _ := t.Size(e.Name)
|
||||
nts = append(nts, NiceTree{
|
||||
Name: e.Name,
|
||||
Mode: mode.String(),
|
||||
IsFile: e.Mode.IsFile(),
|
||||
Size: sz,
|
||||
})
|
||||
}
|
||||
|
||||
return nts
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
module realy.lol/cmd/legit
|
||||
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma/v2 v2.14.0
|
||||
github.com/bluekeyes/go-gitdiff v0.8.0
|
||||
github.com/cyphar/filepath-securejoin v0.4.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/go-git/go-git/v5 v5.13.2
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
golang.org/x/sys v0.30.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.5 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
replace github.com/sergi/go-diff => github.com/sergi/go-diff v1.1.0
|
||||
|
||||
replace github.com/go-git/go-git/v5 => github.com/go-git/go-git/v5 v5.6.1
|
||||
187
cmd/legit/go.sum
187
cmd/legit/go.sum
@@ -1,187 +0,0 @@
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
|
||||
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
|
||||
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
||||
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/bluekeyes/go-gitdiff v0.8.0 h1:Nn1wfw3/XeKoc3lWk+2bEXGUHIx36kj80FM1gVcBk+o=
|
||||
github.com/bluekeyes/go-gitdiff v0.8.0/go.mod h1:WWAk1Mc6EgWarCrPFO+xeYlujPu98VuLW3Tu+B/85AE=
|
||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
|
||||
github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk=
|
||||
github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
@@ -1,23 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Anirudh Oppiliappan <x@icyphox.sh>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"realy.lol/cmd/legit/config"
|
||||
"realy.lol/cmd/legit/routes"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var cfg string
|
||||
flag.StringVar(&cfg, "config", "./config.yaml", "path to config file")
|
||||
flag.Parse()
|
||||
|
||||
c, err := config.Read(cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := UnveilPaths([]string{
|
||||
c.Dirs.Static,
|
||||
c.Repo.ScanPath,
|
||||
c.Dirs.Templates,
|
||||
},
|
||||
"r"); err != nil {
|
||||
log.Fatalf("unveil: %s", err)
|
||||
}
|
||||
|
||||
mux := routes.Handlers(c)
|
||||
addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
|
||||
log.Println("starting server on", addr)
|
||||
log.Fatal(http.ListenAndServe(addr, mux))
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
legit
|
||||
-----
|
||||
|
||||
A git web frontend written in Go.
|
||||
|
||||
Pronounced however you like; I prefer channeling my inner beret-wearing
|
||||
Frenchman, and saying "Oui, il est le git!"
|
||||
|
||||
But yeah it's pretty legit, no cap on god fr fr.
|
||||
|
||||
|
||||
FEATURES
|
||||
|
||||
• Fully customizable templates and stylesheets.
|
||||
• Cloning over http(s).
|
||||
• Less archaic HTML.
|
||||
• Not CGI.
|
||||
|
||||
|
||||
INSTALLING
|
||||
|
||||
Clone it, 'go build' it.
|
||||
|
||||
|
||||
CONFIG
|
||||
|
||||
Uses yaml for configuration. Looks for a 'config.yaml' in the current
|
||||
directory by default; pass the '--config' flag to point it elsewhere.
|
||||
|
||||
Example config.yaml:
|
||||
|
||||
repo:
|
||||
scanPath: /var/www/git
|
||||
readme:
|
||||
- readme
|
||||
- README
|
||||
- readme.md
|
||||
- README.md
|
||||
mainBranch:
|
||||
- master
|
||||
- main
|
||||
ignore:
|
||||
- foo
|
||||
- bar
|
||||
dirs:
|
||||
templates: ./templates
|
||||
static: ./static
|
||||
meta:
|
||||
title: git good
|
||||
description: i think it's a skill issue
|
||||
syntaxHighlight: monokailight
|
||||
server:
|
||||
name: git.icyphox.sh
|
||||
host: 127.0.0.1
|
||||
port: 5555
|
||||
|
||||
These options are fairly self-explanatory, but of note are:
|
||||
|
||||
• repo.scanPath: where all your git repos live (or die). legit doesn't
|
||||
traverse subdirs yet.
|
||||
• dirs: use this to override the default templates and static assets.
|
||||
• repo.readme: readme files to look for.
|
||||
• repo.mainBranch: main branch names to look for.
|
||||
• repo.ignore: repos to ignore, relative to scanPath.
|
||||
• repo.unlisted: repos to hide, relative to scanPath.
|
||||
• server.name: used for go-import meta tags and clone URLs.
|
||||
• meta.syntaxHighlight: this is used to select the syntax theme to render. If left
|
||||
blank or removed, the native theme will be used. If an invalid theme is set in this field,
|
||||
it will default to "monokailight". For more information
|
||||
about themes, please refer to chroma's gallery [1].
|
||||
|
||||
|
||||
NOTES
|
||||
|
||||
• Run legit behind a TLS terminating proxy like relayd(8) or nginx.
|
||||
• Cloning only works in bare repos -- this is a limitation inherent to git. You
|
||||
can still view non-bare repos just fine in legit.
|
||||
• Pushing over https, while supported, is disabled because auth is a
|
||||
pain. Use ssh.
|
||||
• Paths are unveil(2)'d on OpenBSD.
|
||||
• Docker images are available ghcr.io/icyphox/legit:{master,latest,vX.Y.Z}. [2]
|
||||
|
||||
LICENSE
|
||||
|
||||
legit is licensed under MIT.
|
||||
|
||||
[1]: https://swapoff.org/chroma/playground/
|
||||
[2]: https://github.com/icyphox/legit/pkgs/container/legit
|
||||
@@ -1,81 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
|
||||
"realy.lol/cmd/legit/git/service"
|
||||
)
|
||||
|
||||
func (d *deps) InfoRefs(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.PathValue("name")
|
||||
name = filepath.Clean(name)
|
||||
|
||||
repo, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
|
||||
if err != nil {
|
||||
log.Printf("securejoin error: %v", err)
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("content-type", "application/x-git-upload-pack-advertisement")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
cmd := service.ServiceCommand{
|
||||
Dir: repo,
|
||||
Stdout: w,
|
||||
}
|
||||
|
||||
if err := cmd.InfoRefs(); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
log.Printf("git: failed to execute git-upload-pack (info/refs) %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) UploadPack(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.PathValue("name")
|
||||
name = filepath.Clean(name)
|
||||
|
||||
repo, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
|
||||
if err != nil {
|
||||
log.Printf("securejoin error: %v", err)
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("content-type", "application/x-git-upload-pack-result")
|
||||
w.Header().Set("Connection", "Keep-Alive")
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
cmd := service.ServiceCommand{
|
||||
Dir: repo,
|
||||
Stdout: w,
|
||||
}
|
||||
|
||||
var reader io.ReadCloser
|
||||
reader = r.Body
|
||||
|
||||
if r.Header.Get("Content-Encoding") == "gzip" {
|
||||
reader, err := gzip.NewReader(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
log.Printf("git: failed to create gzip reader: %s", err)
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
}
|
||||
|
||||
cmd.Stdin = reader
|
||||
if err := cmd.UploadPack(); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
log.Printf("git: failed to execute git-upload-pack %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"realy.lol/cmd/legit/config"
|
||||
)
|
||||
|
||||
// Checks for gitprotocol-http(5) specific smells; if found, passes
|
||||
// the request on to the git http service, else render the web frontend.
|
||||
func (d *deps) Multiplex(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.PathValue("rest")
|
||||
|
||||
if r.URL.RawQuery == "service=git-receive-pack" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("no pushing allowed!"))
|
||||
return
|
||||
}
|
||||
|
||||
if path == "info/refs" &&
|
||||
r.URL.RawQuery == "service=git-upload-pack" &&
|
||||
r.Method == "GET" {
|
||||
d.InfoRefs(w, r)
|
||||
} else if path == "git-upload-pack" && r.Method == "POST" {
|
||||
d.UploadPack(w, r)
|
||||
} else if r.Method == "GET" {
|
||||
d.RepoIndex(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func Handlers(c *config.Config) *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
d := deps{c}
|
||||
|
||||
mux.HandleFunc("GET /", d.Index)
|
||||
mux.HandleFunc("GET /static/{file}", d.ServeStatic)
|
||||
mux.HandleFunc("GET /{name}", d.Multiplex)
|
||||
mux.HandleFunc("POST /{name}", d.Multiplex)
|
||||
mux.HandleFunc("GET /{name}/tree/{ref}/{rest...}", d.RepoTree)
|
||||
mux.HandleFunc("GET /{name}/blob/{ref}/{rest...}", d.FileContent)
|
||||
mux.HandleFunc("GET /{name}/log/{ref}", d.Log)
|
||||
mux.HandleFunc("GET /{name}/archive/{file}", d.Archive)
|
||||
mux.HandleFunc("GET /{name}/commit/{ref}", d.Diff)
|
||||
mux.HandleFunc("GET /{name}/refs/{$}", d.Refs)
|
||||
mux.HandleFunc("GET /{name}/{rest...}", d.Multiplex)
|
||||
mux.HandleFunc("POST /{name}/{rest...}", d.Multiplex)
|
||||
|
||||
return mux
|
||||
}
|
||||
@@ -1,486 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday/v2"
|
||||
|
||||
"realy.lol/cmd/legit/config"
|
||||
"realy.lol/cmd/legit/git"
|
||||
)
|
||||
|
||||
type deps struct {
|
||||
c *config.Config
|
||||
}
|
||||
|
||||
func (d *deps) Index(w http.ResponseWriter, r *http.Request) {
|
||||
dirs, err := os.ReadDir(d.c.Repo.ScanPath)
|
||||
if err != nil {
|
||||
d.Write500(w)
|
||||
log.Printf("reading scan path: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
type info struct {
|
||||
DisplayName, Name, Desc, Idle string
|
||||
d time.Time
|
||||
}
|
||||
|
||||
infos := []info{}
|
||||
|
||||
for _, dir := range dirs {
|
||||
name := dir.Name()
|
||||
if !dir.IsDir() || d.isIgnored(name) || d.isUnlisted(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
|
||||
if err != nil {
|
||||
log.Printf("securejoin error: %v", err)
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
gr, err := git.Open(path, "")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
c, err := gr.LastCommit()
|
||||
if err != nil {
|
||||
d.Write500(w)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
infos = append(infos, info{
|
||||
DisplayName: getDisplayName(name),
|
||||
Name: name,
|
||||
Desc: getDescription(path),
|
||||
Idle: humanize.Time(c.Author.When),
|
||||
d: c.Author.When,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(infos, func(i, j int) bool {
|
||||
return infos[j].d.Before(infos[i].d)
|
||||
})
|
||||
|
||||
tpath := filepath.Join(d.c.Dirs.Templates, "*")
|
||||
t := template.Must(template.ParseGlob(tpath))
|
||||
|
||||
data := make(map[string]interface{})
|
||||
data["meta"] = d.c.Meta
|
||||
data["info"] = infos
|
||||
|
||||
if err := t.ExecuteTemplate(w, "index", data); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) RepoIndex(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.PathValue("name")
|
||||
if d.isIgnored(name) {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
name = filepath.Clean(name)
|
||||
path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
|
||||
if err != nil {
|
||||
log.Printf("securejoin error: %v", err)
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
gr, err := git.Open(path, "")
|
||||
if err != nil {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
commits, err := gr.Commits()
|
||||
if err != nil {
|
||||
d.Write500(w)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var readmeContent template.HTML
|
||||
for _, readme := range d.c.Repo.Readme {
|
||||
ext := filepath.Ext(readme)
|
||||
content, _ := gr.FileContent(readme)
|
||||
if len(content) > 0 {
|
||||
switch ext {
|
||||
case ".md", ".mkd", ".markdown":
|
||||
unsafe := blackfriday.Run(
|
||||
[]byte(content),
|
||||
blackfriday.WithExtensions(blackfriday.CommonExtensions),
|
||||
)
|
||||
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
||||
readmeContent = template.HTML(html)
|
||||
default:
|
||||
safe := bluemonday.UGCPolicy().SanitizeBytes([]byte(content))
|
||||
readmeContent = template.HTML(
|
||||
fmt.Sprintf(`<pre>%s</pre>`, safe),
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if readmeContent == "" {
|
||||
log.Printf("no readme found for %s", name)
|
||||
}
|
||||
|
||||
mainBranch, err := gr.FindMainBranch(d.c.Repo.MainBranch)
|
||||
if err != nil {
|
||||
d.Write500(w)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
tpath := filepath.Join(d.c.Dirs.Templates, "*")
|
||||
t := template.Must(template.ParseGlob(tpath))
|
||||
|
||||
if len(commits) >= 3 {
|
||||
commits = commits[:3]
|
||||
}
|
||||
|
||||
data := make(map[string]any)
|
||||
data["name"] = name
|
||||
data["displayname"] = getDisplayName(name)
|
||||
data["ref"] = mainBranch
|
||||
data["readme"] = readmeContent
|
||||
data["commits"] = commits
|
||||
data["desc"] = getDescription(path)
|
||||
data["servername"] = d.c.Server.Name
|
||||
data["meta"] = d.c.Meta
|
||||
data["gomod"] = isGoModule(gr)
|
||||
|
||||
if err := t.ExecuteTemplate(w, "repo", data); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *deps) RepoTree(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.PathValue("name")
|
||||
if d.isIgnored(name) {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
treePath := r.PathValue("rest")
|
||||
ref := r.PathValue("ref")
|
||||
|
||||
name = filepath.Clean(name)
|
||||
path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
|
||||
if err != nil {
|
||||
log.Printf("securejoin error: %v", err)
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
gr, err := git.Open(path, ref)
|
||||
if err != nil {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
files, err := gr.FileTree(treePath)
|
||||
if err != nil {
|
||||
d.Write500(w)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
data := make(map[string]any)
|
||||
data["name"] = name
|
||||
data["displayname"] = getDisplayName(name)
|
||||
data["ref"] = ref
|
||||
data["parent"] = treePath
|
||||
data["desc"] = getDescription(path)
|
||||
data["dotdot"] = filepath.Dir(treePath)
|
||||
|
||||
d.listFiles(files, data, w)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *deps) FileContent(w http.ResponseWriter, r *http.Request) {
|
||||
var raw bool
|
||||
if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
|
||||
raw = rawParam
|
||||
}
|
||||
|
||||
name := r.PathValue("name")
|
||||
if d.isIgnored(name) {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
treePath := r.PathValue("rest")
|
||||
ref := r.PathValue("ref")
|
||||
|
||||
name = filepath.Clean(name)
|
||||
path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
|
||||
if err != nil {
|
||||
log.Printf("securejoin error: %v", err)
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
gr, err := git.Open(path, ref)
|
||||
if err != nil {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
contents, err := gr.FileContent(treePath)
|
||||
if err != nil {
|
||||
d.Write500(w)
|
||||
return
|
||||
}
|
||||
data := make(map[string]any)
|
||||
data["name"] = name
|
||||
data["displayname"] = getDisplayName(name)
|
||||
data["ref"] = ref
|
||||
data["desc"] = getDescription(path)
|
||||
data["path"] = treePath
|
||||
|
||||
if raw {
|
||||
d.showRaw(contents, w)
|
||||
} else {
|
||||
if d.c.Meta.SyntaxHighlight == "" {
|
||||
d.showFile(contents, data, w)
|
||||
} else {
|
||||
d.showFileWithHighlight(treePath, contents, data, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) Archive(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.PathValue("name")
|
||||
if d.isIgnored(name) {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
file := r.PathValue("file")
|
||||
|
||||
// TODO: extend this to add more files compression (e.g.: xz)
|
||||
if !strings.HasSuffix(file, ".tar.gz") {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
ref := strings.TrimSuffix(file, ".tar.gz")
|
||||
|
||||
// This allows the browser to use a proper name for the file when
|
||||
// downloading
|
||||
filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
|
||||
setContentDisposition(w, filename)
|
||||
setGZipMIME(w)
|
||||
|
||||
path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
|
||||
if err != nil {
|
||||
log.Printf("securejoin error: %v", err)
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
gr, err := git.Open(path, ref)
|
||||
if err != nil {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
gw := gzip.NewWriter(w)
|
||||
defer gw.Close()
|
||||
|
||||
prefix := fmt.Sprintf("%s-%s", name, ref)
|
||||
err = gr.WriteTar(gw, prefix)
|
||||
if err != nil {
|
||||
// once we start writing to the body we can't report error anymore
|
||||
// so we are only left with printing the error.
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = gw.Flush()
|
||||
if err != nil {
|
||||
// once we start writing to the body we can't report error anymore
|
||||
// so we are only left with printing the error.
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) Log(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.PathValue("name")
|
||||
if d.isIgnored(name) {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
ref := r.PathValue("ref")
|
||||
|
||||
path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
|
||||
if err != nil {
|
||||
log.Printf("securejoin error: %v", err)
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
gr, err := git.Open(path, ref)
|
||||
if err != nil {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
commits, err := gr.Commits()
|
||||
if err != nil {
|
||||
d.Write500(w)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
tpath := filepath.Join(d.c.Dirs.Templates, "*")
|
||||
t := template.Must(template.ParseGlob(tpath))
|
||||
|
||||
data := make(map[string]interface{})
|
||||
data["commits"] = commits
|
||||
data["meta"] = d.c.Meta
|
||||
data["name"] = name
|
||||
data["displayname"] = getDisplayName(name)
|
||||
data["ref"] = ref
|
||||
data["desc"] = getDescription(path)
|
||||
data["log"] = true
|
||||
|
||||
if err := t.ExecuteTemplate(w, "log", data); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) Diff(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.PathValue("name")
|
||||
if d.isIgnored(name) {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
ref := r.PathValue("ref")
|
||||
|
||||
path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
|
||||
if err != nil {
|
||||
log.Printf("securejoin error: %v", err)
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
gr, err := git.Open(path, ref)
|
||||
if err != nil {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
diff, err := gr.Diff()
|
||||
if err != nil {
|
||||
d.Write500(w)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
tpath := filepath.Join(d.c.Dirs.Templates, "*")
|
||||
t := template.Must(template.ParseGlob(tpath))
|
||||
|
||||
data := make(map[string]interface{})
|
||||
|
||||
data["commit"] = diff.Commit
|
||||
data["stat"] = diff.Stat
|
||||
data["diff"] = diff.Diff
|
||||
data["meta"] = d.c.Meta
|
||||
data["name"] = name
|
||||
data["displayname"] = getDisplayName(name)
|
||||
data["ref"] = ref
|
||||
data["desc"] = getDescription(path)
|
||||
|
||||
if err := t.ExecuteTemplate(w, "commit", data); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) Refs(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.PathValue("name")
|
||||
if d.isIgnored(name) {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
path, err := securejoin.SecureJoin(d.c.Repo.ScanPath, name)
|
||||
if err != nil {
|
||||
log.Printf("securejoin error: %v", err)
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
gr, err := git.Open(path, "")
|
||||
if err != nil {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
tags, err := gr.Tags()
|
||||
if err != nil {
|
||||
// Non-fatal, we *should* have at least one branch to show.
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
branches, err := gr.Branches()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
d.Write500(w)
|
||||
return
|
||||
}
|
||||
|
||||
tpath := filepath.Join(d.c.Dirs.Templates, "*")
|
||||
t := template.Must(template.ParseGlob(tpath))
|
||||
|
||||
data := make(map[string]interface{})
|
||||
|
||||
data["meta"] = d.c.Meta
|
||||
data["name"] = name
|
||||
data["displayname"] = getDisplayName(name)
|
||||
data["branches"] = branches
|
||||
data["tags"] = tags
|
||||
data["desc"] = getDescription(path)
|
||||
|
||||
if err := t.ExecuteTemplate(w, "refs", data); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) {
|
||||
f := r.PathValue("file")
|
||||
f = filepath.Clean(f)
|
||||
f, err := securejoin.SecureJoin(d.c.Dirs.Static, f)
|
||||
if err != nil {
|
||||
d.Write404(w)
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, f)
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
|
||||
"realy.lol/cmd/legit/git"
|
||||
)
|
||||
|
||||
func (d *deps) Write404(w http.ResponseWriter) {
|
||||
tpath := filepath.Join(d.c.Dirs.Templates, "*")
|
||||
t := template.Must(template.ParseGlob(tpath))
|
||||
w.WriteHeader(404)
|
||||
if err := t.ExecuteTemplate(w, "404", nil); err != nil {
|
||||
log.Printf("404 template: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) Write500(w http.ResponseWriter) {
|
||||
tpath := filepath.Join(d.c.Dirs.Templates, "*")
|
||||
t := template.Must(template.ParseGlob(tpath))
|
||||
w.WriteHeader(500)
|
||||
if err := t.ExecuteTemplate(w, "500", nil); err != nil {
|
||||
log.Printf("500 template: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) listFiles(files []git.NiceTree, data map[string]any, w http.ResponseWriter) {
|
||||
tpath := filepath.Join(d.c.Dirs.Templates, "*")
|
||||
t := template.Must(template.ParseGlob(tpath))
|
||||
|
||||
data["files"] = files
|
||||
data["meta"] = d.c.Meta
|
||||
|
||||
if err := t.ExecuteTemplate(w, "tree", data); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func countLines(r io.Reader) (int, error) {
|
||||
buf := make([]byte, 32*1024)
|
||||
bufLen := 0
|
||||
count := 0
|
||||
nl := []byte{'\n'}
|
||||
|
||||
for {
|
||||
c, err := r.Read(buf)
|
||||
if c > 0 {
|
||||
bufLen += c
|
||||
}
|
||||
count += bytes.Count(buf[:c], nl)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
/* handle last line not having a newline at the end */
|
||||
if bufLen >= 1 && buf[(bufLen-1)%(32*1024)] != '\n' {
|
||||
count++
|
||||
}
|
||||
return count, nil
|
||||
case err != nil:
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) showFileWithHighlight(name, content string, data map[string]any, w http.ResponseWriter) {
|
||||
tpath := filepath.Join(d.c.Dirs.Templates, "*")
|
||||
t := template.Must(template.ParseGlob(tpath))
|
||||
|
||||
lexer := lexers.Get(name)
|
||||
if lexer == nil {
|
||||
lexer = lexers.Get(".txt")
|
||||
}
|
||||
|
||||
style := styles.Get(d.c.Meta.SyntaxHighlight)
|
||||
if style == nil {
|
||||
style = styles.Get("monokailight")
|
||||
}
|
||||
|
||||
formatter := html.New(
|
||||
html.WithLineNumbers(true),
|
||||
html.WithLinkableLineNumbers(true, "L"),
|
||||
)
|
||||
|
||||
iterator, err := lexer.Tokenise(nil, content)
|
||||
if err != nil {
|
||||
d.Write500(w)
|
||||
return
|
||||
}
|
||||
|
||||
var code bytes.Buffer
|
||||
err = formatter.Format(&code, style, iterator)
|
||||
if err != nil {
|
||||
d.Write500(w)
|
||||
return
|
||||
}
|
||||
|
||||
data["content"] = template.HTML(code.String())
|
||||
data["meta"] = d.c.Meta
|
||||
data["chroma"] = true
|
||||
|
||||
if err := t.ExecuteTemplate(w, "file", data); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) showFile(content string, data map[string]any, w http.ResponseWriter) {
|
||||
tpath := filepath.Join(d.c.Dirs.Templates, "*")
|
||||
t := template.Must(template.ParseGlob(tpath))
|
||||
|
||||
lc, err := countLines(strings.NewReader(content))
|
||||
if err != nil {
|
||||
// Non-fatal, we'll just skip showing line numbers in the template.
|
||||
log.Printf("counting lines: %s", err)
|
||||
}
|
||||
|
||||
lines := make([]int, lc)
|
||||
if lc > 0 {
|
||||
for i := range lines {
|
||||
lines[i] = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
data["linecount"] = lines
|
||||
data["content"] = content
|
||||
data["meta"] = d.c.Meta
|
||||
data["chroma"] = false
|
||||
|
||||
if err := t.ExecuteTemplate(w, "file", data); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *deps) showRaw(content string, w http.ResponseWriter) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(content))
|
||||
return
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"realy.lol/cmd/legit/git"
|
||||
)
|
||||
|
||||
func isGoModule(gr *git.GitRepo) bool {
|
||||
_, err := gr.FileContent("go.mod")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func getDisplayName(name string) string {
|
||||
return strings.TrimSuffix(name, ".git")
|
||||
}
|
||||
|
||||
func getDescription(path string) (desc string) {
|
||||
db, err := os.ReadFile(filepath.Join(path, "description"))
|
||||
if err == nil {
|
||||
desc = string(db)
|
||||
} else {
|
||||
desc = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *deps) isUnlisted(name string) bool {
|
||||
for _, i := range d.c.Repo.Unlisted {
|
||||
if name == i {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *deps) isIgnored(name string) bool {
|
||||
for _, i := range d.c.Repo.Ignore {
|
||||
if name == i {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type repoInfo struct {
|
||||
Git *git.GitRepo
|
||||
Path string
|
||||
Category string
|
||||
}
|
||||
|
||||
func (d *deps) getAllRepos() ([]repoInfo, error) {
|
||||
repos := []repoInfo{}
|
||||
max := strings.Count(d.c.Repo.ScanPath, string(os.PathSeparator)) + 2
|
||||
|
||||
err := filepath.WalkDir(d.c.Repo.ScanPath, func(path string, de fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if de.IsDir() {
|
||||
// Check if we've exceeded our recursion depth
|
||||
if strings.Count(path, string(os.PathSeparator)) > max {
|
||||
return fs.SkipDir
|
||||
}
|
||||
|
||||
if d.isIgnored(path) {
|
||||
return fs.SkipDir
|
||||
}
|
||||
|
||||
// A bare repo should always have at least a HEAD file, if it
|
||||
// doesn't we can continue recursing
|
||||
if _, err := os.Lstat(filepath.Join(path, "HEAD")); err == nil {
|
||||
repo, err := git.Open(path, "")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
relpath, _ := filepath.Rel(d.c.Repo.ScanPath, path)
|
||||
repos = append(repos, repoInfo{
|
||||
Git: repo,
|
||||
Path: relpath,
|
||||
Category: d.category(path),
|
||||
})
|
||||
// Since we found a Git repo, we don't want to recurse
|
||||
// further
|
||||
return fs.SkipDir
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return repos, err
|
||||
}
|
||||
|
||||
func (d *deps) category(path string) string {
|
||||
return strings.TrimPrefix(filepath.Dir(strings.TrimPrefix(path, d.c.Repo.ScanPath)), string(os.PathSeparator))
|
||||
}
|
||||
|
||||
func setContentDisposition(w http.ResponseWriter, name string) {
|
||||
h := "inline; filename=\"" + name + "\""
|
||||
w.Header().Add("Content-Disposition", h)
|
||||
}
|
||||
|
||||
func setGZipMIME(w http.ResponseWriter) {
|
||||
setMIME(w, "application/gzip")
|
||||
}
|
||||
|
||||
func setMIME(w http.ResponseWriter, mime string) {
|
||||
w.Header().Add("Content-Type", mime)
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,332 +0,0 @@
|
||||
:root {
|
||||
--white: #fff;
|
||||
--light: #f4f4f4;
|
||||
--cyan: #509c93;
|
||||
--light-gray: #eee;
|
||||
--medium-gray: #ddd;
|
||||
--gray: #6a6a6a;
|
||||
--dark: #444;
|
||||
--darker: #222;
|
||||
|
||||
--sans-font: -apple-system, BlinkMacSystemFont, "Inter", "Roboto", "Segoe UI", sans-serif;
|
||||
--display-font: -apple-system, BlinkMacSystemFont, "Inter", "Roboto", "Segoe UI", sans-serif;
|
||||
--mono-font: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', 'Roboto Mono', Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
color-scheme: dark light;
|
||||
--light: #181818;
|
||||
--cyan: #76c7c0;
|
||||
--light-gray: #333;
|
||||
--medium-gray: #444;
|
||||
--gray: #aaa;
|
||||
--dark: #ddd;
|
||||
--darker: #f4f4f4;
|
||||
--white: #000;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
background: var(--white);
|
||||
-webkit-text-size-adjust: none;
|
||||
font-family: var(--sans-font);
|
||||
font-weight: 380;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: var(--mono-font);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--medium-gray);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 1000px;
|
||||
padding: 0 13px;
|
||||
margin: 40px auto;
|
||||
}
|
||||
|
||||
main, footer {
|
||||
font-size: 1rem;
|
||||
padding: 0;
|
||||
line-height: 160%;
|
||||
}
|
||||
|
||||
header h1, h2, h3 {
|
||||
font-family: var(--display-font);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
main h1 {
|
||||
padding: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
main h2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
main h2, h3 {
|
||||
padding: 20px 0 15px 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
padding: 0.4rem 0 1.5rem 0;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
padding-right: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
a {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
text-decoration: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--darker);
|
||||
border-bottom: 1.5px solid var(--medium-gray);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
border-bottom: 1.5px solid var(--gray);
|
||||
}
|
||||
|
||||
.index {
|
||||
padding-top: 2em;
|
||||
display: grid;
|
||||
grid-template-columns: 6em 1fr minmax(0, 7em);
|
||||
grid-row-gap: 0.5em;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.clone-url {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.clone-url pre {
|
||||
color: var(--dark);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-weight: normal;
|
||||
color: var(--gray);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tree {
|
||||
display: grid;
|
||||
grid-template-columns: 10ch auto 1fr;
|
||||
grid-row-gap: 0.5em;
|
||||
grid-column-gap: 1em;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.log {
|
||||
display: grid;
|
||||
grid-template-columns: 20rem minmax(0, 1fr);
|
||||
grid-row-gap: 0.8em;
|
||||
grid-column-gap: 8rem;
|
||||
margin-bottom: 2em;
|
||||
padding-bottom: 1em;
|
||||
border-bottom: 1.5px solid var(--medium-gray);
|
||||
}
|
||||
|
||||
.log pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.mode, .size {
|
||||
font-family: var(--mono-font);
|
||||
}
|
||||
.size {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.readme pre {
|
||||
white-space: pre-wrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.readme {
|
||||
background: var(--light-gray);
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.readme ul {
|
||||
padding: revert;
|
||||
}
|
||||
|
||||
.readme img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.diff {
|
||||
margin: 1rem 0 1rem 0;
|
||||
padding: 1rem 0 1rem 0;
|
||||
border-bottom: 1.5px solid var(--medium-gray);
|
||||
}
|
||||
|
||||
.diff pre {
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.diff-stat {
|
||||
padding: 1rem 0 1rem 0;
|
||||
}
|
||||
|
||||
.commit-hash, .commit-email {
|
||||
font-family: var(--mono-font);
|
||||
}
|
||||
|
||||
.commit-email:before {
|
||||
content: '<';
|
||||
}
|
||||
|
||||
.commit-email:after {
|
||||
content: '>';
|
||||
}
|
||||
|
||||
.commit {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.commit pre {
|
||||
padding-bottom: 1rem;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.diff-stat ul li {
|
||||
list-style: none;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.diff-add {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.diff-del {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.diff-noop {
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.ref {
|
||||
font-family: var(--sans-font);
|
||||
font-size: 14px;
|
||||
color: var(--gray);
|
||||
display: inline-block;
|
||||
padding-top: 0.7em;
|
||||
}
|
||||
|
||||
.refs pre {
|
||||
white-space: pre-wrap;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.refs strong {
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.line-numbers {
|
||||
white-space: pre-line;
|
||||
-moz-user-select: -moz-none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
float: left;
|
||||
flex-direction: column;
|
||||
margin-right: 1ch;
|
||||
}
|
||||
|
||||
.file-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
grid-template-columns: 1rem minmax(0, 1fr);
|
||||
gap: 1rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--light-gray);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.chroma-file-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
grid-template-columns: 1rem minmax(0, 1fr);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.file-content {
|
||||
background: var(--light-gray);
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.diff-type {
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.commit-info {
|
||||
color: var(--gray);
|
||||
padding-bottom: 1.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.index {
|
||||
grid-row-gap: 0.8em;
|
||||
}
|
||||
|
||||
.log {
|
||||
grid-template-columns: 1fr;
|
||||
grid-row-gap: 0em;
|
||||
}
|
||||
|
||||
.index {
|
||||
grid-template-columns: 1fr;
|
||||
grid-row-gap: 0em;
|
||||
}
|
||||
|
||||
.index-name:not(:first-child) {
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
.commit-info:not(:last-child) {
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{{ define "404" }}
|
||||
<html>
|
||||
<title>404</title>
|
||||
{{ template "head" . }}
|
||||
<body>
|
||||
{{ template "nav" . }}
|
||||
<main>
|
||||
<h3>404 — nothing like that here.</h3>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
{{ end }}
|
||||
@@ -1,13 +0,0 @@
|
||||
{{ define "500" }}
|
||||
<html>
|
||||
<title>500</title>
|
||||
{{ template "head" . }}
|
||||
<body>
|
||||
{{ template "nav" . }}
|
||||
<main>
|
||||
<h3>500 — something broke!</h3>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
{{ end }}
|
||||
@@ -1,104 +0,0 @@
|
||||
{{ define "commit" }}
|
||||
<html>
|
||||
{{ template "head" . }}
|
||||
|
||||
{{ template "repoheader" . }}
|
||||
<body>
|
||||
{{ template "nav" . }}
|
||||
<main>
|
||||
<section class="commit">
|
||||
<pre>
|
||||
{{- .commit.Message -}}
|
||||
</pre>
|
||||
<div class="commit-info">
|
||||
{{ .commit.Author.Name }} <a href="mailto:{{ .commit.Author.Email }}" class="commit-email">{{ .commit.Author.Email}}</a>
|
||||
<div>{{ .commit.Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>commit</strong>
|
||||
<p><a href="/{{ .name }}/commit/{{ .commit.This }}" class="commit-hash">
|
||||
{{ .commit.This }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{ if .commit.Parent }}
|
||||
<div>
|
||||
<strong>parent</strong>
|
||||
<p><a href="/{{ .name }}/commit/{{ .commit.Parent }}" class="commit-hash">
|
||||
{{ .commit.Parent }}
|
||||
</a></p>
|
||||
</div>
|
||||
|
||||
{{ end }}
|
||||
<div class="diff-stat">
|
||||
<div>
|
||||
{{ .stat.FilesChanged }} files changed,
|
||||
{{ .stat.Insertions }} insertions(+),
|
||||
{{ .stat.Deletions }} deletions(-)
|
||||
</div>
|
||||
<div>
|
||||
<br>
|
||||
<strong>jump to</strong>
|
||||
{{ range .diff }}
|
||||
<ul>
|
||||
<li><a href="#{{ .Name.New }}">{{ .Name.New }}</a></li>
|
||||
</ul>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
{{ $repo := .name }}
|
||||
{{ $this := .commit.This }}
|
||||
{{ $parent := .commit.Parent }}
|
||||
{{ range .diff }}
|
||||
<div id="{{ .Name.New }}">
|
||||
<div class="diff">
|
||||
{{ if .IsNew }}
|
||||
<span class="diff-type">A</span>
|
||||
{{ end }}
|
||||
{{ if .IsDelete }}
|
||||
<span class="diff-type">D</span>
|
||||
{{ end }}
|
||||
{{ if not (or .IsNew .IsDelete) }}
|
||||
<span class="diff-type">M</span>
|
||||
{{ end }}
|
||||
{{ if .Name.Old }}
|
||||
<a href="/{{ $repo }}/blob/{{ $parent }}/{{ .Name.Old }}">{{ .Name.Old }}</a>
|
||||
{{ if .Name.New }}
|
||||
→
|
||||
<a href="/{{ $repo }}/blob/{{ $this }}/{{ .Name.New }}">{{ .Name.New }}</a>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<a href="/{{ $repo }}/blob/{{ $this }}/{{ .Name.New }}">{{ .Name.New }}</a>
|
||||
{{- end -}}
|
||||
{{ if .IsBinary }}
|
||||
<p>Not showing binary file.</p>
|
||||
{{ else }}
|
||||
<pre>
|
||||
{{- range .TextFragments -}}
|
||||
<p>{{- .Header -}}</p>
|
||||
{{- range .Lines -}}
|
||||
{{- if eq .Op.String "+" -}}
|
||||
<span class="diff-add">{{ .String }}</span>
|
||||
{{- end -}}
|
||||
{{- if eq .Op.String "-" -}}
|
||||
<span class="diff-del">{{ .String }}</span>
|
||||
{{- end -}}
|
||||
{{- if eq .Op.String " " -}}
|
||||
<span class="diff-noop">{{ .String }}</span>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
||||
@@ -1,36 +0,0 @@
|
||||
{{ define "file" }}
|
||||
<html>
|
||||
{{ template "head" . }}
|
||||
{{ template "repoheader" . }}
|
||||
<body>
|
||||
{{ template "nav" . }}
|
||||
<main>
|
||||
<p>{{ .path }} (<a style="color: gray" href="?raw=true">view raw</a>)</p>
|
||||
{{if .chroma }}
|
||||
<div class="chroma-file-wrapper">
|
||||
{{ .content }}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="file-wrapper">
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td class="line-numbers">
|
||||
<pre>
|
||||
{{- range .linecount }}
|
||||
<a id="L{{ . }}" href="#L{{ . }}">{{ . }}</a>
|
||||
{{- end -}}
|
||||
</pre>
|
||||
</td>
|
||||
<td class="file-content">
|
||||
<pre>
|
||||
{{- .content -}}
|
||||
</pre>
|
||||
</td>
|
||||
</tbody></tr>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
||||
@@ -1,32 +0,0 @@
|
||||
{{ define "head" }}
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/static/style.css" type="text/css">
|
||||
<link rel="icon" type="image/png" size="32x32" href="/static/legit.png">
|
||||
{{ if .parent }}
|
||||
<title>{{ .meta.Title }} — {{ .name }} ({{ .ref }}): {{ .parent }}/</title>
|
||||
|
||||
{{ else if .path }}
|
||||
<title>{{ .meta.Title }} — {{ .name }} ({{ .ref }}): {{ .path }}</title>
|
||||
{{ else if .files }}
|
||||
<title>{{ .meta.Title }} — {{ .name }} ({{ .ref }})</title>
|
||||
{{ else if .commit }}
|
||||
<title>{{ .meta.Title }} — {{ .name }}: {{ .commit.This }}</title>
|
||||
{{ else if .branches }}
|
||||
<title>{{ .meta.Title }} — {{ .name }}: refs</title>
|
||||
{{ else if .commits }}
|
||||
{{ if .log }}
|
||||
<title>{{ .meta.Title }} — {{ .name }}: log</title>
|
||||
{{ else }}
|
||||
<title>{{ .meta.Title }} — {{ .name }}</title>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<title>{{ .meta.Title }}</title>
|
||||
{{ end }}
|
||||
{{ if and .servername .gomod }}
|
||||
<meta name="go-import" content="{{ .servername}}/{{ .name }} git https://{{ .servername }}/{{ .name }}">
|
||||
{{ end }}
|
||||
<!-- other meta tags here -->
|
||||
</head>
|
||||
{{ end }}
|
||||
@@ -1,21 +0,0 @@
|
||||
{{ define "index" }}
|
||||
<html>
|
||||
{{ template "head" . }}
|
||||
|
||||
<header>
|
||||
<h1>{{ .meta.Title }}</h1>
|
||||
<h2>{{ .meta.Description }}</h2>
|
||||
</header>
|
||||
<body>
|
||||
<main>
|
||||
<div class="index">
|
||||
{{ range .info }}
|
||||
<div class="index-name"><a href="/{{ .Name }}">{{ .DisplayName }}</a></div>
|
||||
<div class="desc">{{ .Desc }}</div>
|
||||
<div>{{ .Idle }}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
||||
@@ -1,25 +0,0 @@
|
||||
{{ define "log" }}
|
||||
<html>
|
||||
{{ template "head" . }}
|
||||
|
||||
{{ template "repoheader" . }}
|
||||
<body>
|
||||
{{ template "nav" . }}
|
||||
<main>
|
||||
{{ $repo := .name }}
|
||||
<div class="log">
|
||||
{{ range .commits }}
|
||||
<div>
|
||||
<div><a href="/{{ $repo }}/commit/{{ .Hash.String }}" class="commit-hash">{{ slice .Hash.String 0 8 }}</a></div>
|
||||
<pre>{{ .Message }}</pre>
|
||||
</div>
|
||||
<div class="commit-info">
|
||||
{{ .Author.Name }} <a href="mailto:{{ .Author.Email }}" class="commit-email">{{ .Author.Email }}</a>
|
||||
<div>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
||||
@@ -1,14 +0,0 @@
|
||||
{{ define "nav" }}
|
||||
<nav>
|
||||
<ul>
|
||||
{{ if .name }}
|
||||
<li><a href="/{{ .name }}">summary</a>
|
||||
<li><a href="/{{ .name }}/refs">refs</a>
|
||||
{{ if .ref }}
|
||||
<li><a href="/{{ .name }}/tree/{{ .ref }}/">tree</a>
|
||||
<li><a href="/{{ .name }}/log/{{ .ref }}">log</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
</nav>
|
||||
{{ end }}
|
||||
@@ -1,40 +0,0 @@
|
||||
{{ define "refs" }}
|
||||
<html>
|
||||
{{ template "head" . }}
|
||||
|
||||
{{ template "repoheader" . }}
|
||||
<body>
|
||||
{{ template "nav" . }}
|
||||
<main>
|
||||
{{ $name := .name }}
|
||||
<h3>branches</h3>
|
||||
<div class="refs">
|
||||
{{ range .branches }}
|
||||
<div>
|
||||
<strong>{{ .Name.Short }}</strong>
|
||||
<a href="/{{ $name }}/tree/{{ .Name.Short }}/">browse</a>
|
||||
<a href="/{{ $name }}/log/{{ .Name.Short }}">log</a>
|
||||
<a href="/{{ $name }}/archive/{{ .Name.Short }}.tar.gz">tar.gz</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ if .tags }}
|
||||
<h3>tags</h3>
|
||||
<div class="refs">
|
||||
{{ range .tags }}
|
||||
<div>
|
||||
<strong>{{ .Name }}</strong>
|
||||
<a href="/{{ $name }}/tree/{{ .Name }}/">browse</a>
|
||||
<a href="/{{ $name }}/log/{{ .Name }}">log</a>
|
||||
<a href="/{{ $name }}/archive/{{ .Name }}.tar.gz">tar.gz</a>
|
||||
{{ if .Message }}
|
||||
<pre>{{ .Message }}</pre>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
||||
@@ -1,12 +0,0 @@
|
||||
{{ define "repoheader" }}
|
||||
<header>
|
||||
<h2>
|
||||
<a href="/">all repos</a>
|
||||
— {{ .displayname }}
|
||||
{{ if .ref }}
|
||||
<span class="ref">@ {{ .ref }}</span>
|
||||
{{ end }}
|
||||
</h2>
|
||||
<h3 class="desc">{{ .desc }}</h3>
|
||||
</header>
|
||||
{{ end }}
|
||||
@@ -1,38 +0,0 @@
|
||||
{{ define "repo" }}
|
||||
<html>
|
||||
{{ template "head" . }}
|
||||
|
||||
{{ template "repoheader" . }}
|
||||
|
||||
<body>
|
||||
{{ template "nav" . }}
|
||||
<main>
|
||||
{{ $repo := .name }}
|
||||
<div class="log">
|
||||
{{ range .commits }}
|
||||
<div>
|
||||
<div><a href="/{{ $repo }}/commit/{{ .Hash.String }}" class="commit-hash">{{ slice .Hash.String 0 8 }}</a></div>
|
||||
<pre>{{ .Message }}</pre>
|
||||
</div>
|
||||
<div class="commit-info">
|
||||
{{ .Author.Name }} <a href="mailto:{{ .Author.Email }}" class="commit-email">{{ .Author.Email }}</a>
|
||||
<div>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{- if .readme }}
|
||||
<article class="readme">
|
||||
{{- .readme -}}
|
||||
</article>
|
||||
{{- end -}}
|
||||
|
||||
<div class="clone-url">
|
||||
<strong>clone</strong>
|
||||
<pre>
|
||||
git clone https://{{ .servername }}/{{ .name }}
|
||||
</pre>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
||||
@@ -1,55 +0,0 @@
|
||||
{{ define "tree" }}
|
||||
<html>
|
||||
|
||||
{{ template "head" . }}
|
||||
|
||||
{{ template "repoheader" . }}
|
||||
<body>
|
||||
{{ template "nav" . }}
|
||||
<main>
|
||||
{{ $repo := .name }}
|
||||
{{ $ref := .ref }}
|
||||
{{ $parent := .parent }}
|
||||
|
||||
<div class="tree">
|
||||
{{ if $parent }}
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div><a href="/{{ $repo }}/tree/{{ $ref }}/{{ .dotdot }}">..</a></div>
|
||||
{{ end }}
|
||||
{{ range .files }}
|
||||
{{ if not .IsFile }}
|
||||
<div class="mode">{{ .Mode }}</div>
|
||||
<div class="size">{{ .Size }}</div>
|
||||
<div>
|
||||
{{ if $parent }}
|
||||
<a href="/{{ $repo }}/tree/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a>
|
||||
{{ else }}
|
||||
<a href="/{{ $repo }}/tree/{{ $ref }}/{{ .Name }}">{{ .Name }}/</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ range .files }}
|
||||
{{ if .IsFile }}
|
||||
<div class="mode">{{ .Mode }}</div>
|
||||
<div class="size">{{ .Size }}</div>
|
||||
<div>
|
||||
{{ if $parent }}
|
||||
<a href="/{{ $repo }}/blob/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a>
|
||||
{{ else }}
|
||||
<a href="/{{ $repo }}/blob/{{ $ref }}/{{ .Name }}">{{ .Name }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
<article>
|
||||
<pre>
|
||||
{{- if .readme }}{{ .readme }}{{- end -}}
|
||||
</pre>
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
||||
@@ -1,29 +0,0 @@
|
||||
//go:build openbsd
|
||||
// +build openbsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func Unveil(path string, perms string) error {
|
||||
log.Printf("unveil: \"%s\", %s", path, perms)
|
||||
return unix.Unveil(path, perms)
|
||||
}
|
||||
|
||||
func UnveilBlock() error {
|
||||
log.Printf("unveil: block")
|
||||
return unix.UnveilBlock()
|
||||
}
|
||||
|
||||
func UnveilPaths(paths []string, perms string) error {
|
||||
for _, path := range paths {
|
||||
if err := Unveil(path, perms); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return UnveilBlock()
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
//go:build !openbsd
|
||||
// +build !openbsd
|
||||
|
||||
// Stub functions for GOOS that don't support unix.Unveil()
|
||||
|
||||
package main
|
||||
|
||||
func Unveil(path string, perms string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func UnveilBlock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func UnveilPaths(paths []string, perms string) error {
|
||||
return nil
|
||||
}
|
||||
1
go.mod
1
go.mod
@@ -46,6 +46,7 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rickb777/acceptable v0.46.0 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 // indirect
|
||||
github.com/templexxx/cpu v0.1.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -87,6 +87,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rickb777/acceptable v0.46.0 h1:G/GoAKyQlDKKmEl9kbp9mgkf4vHvcxB6Vx/l2MHEdJ8=
|
||||
github.com/rickb777/acceptable v0.46.0/go.mod h1:MHPJml//8ogaImxiVu6qmOlpoFiKtuLOaTEGSl+Mmos=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
|
||||
@@ -16,8 +16,9 @@ import (
|
||||
|
||||
// ConfigurationSetInput is the parameters for HTTP API method to set Configuration.
|
||||
type ConfigurationSetInput struct {
|
||||
Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"true"`
|
||||
Body config.C `doc:"the new configuration"`
|
||||
Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"true"`
|
||||
Body config.C `doc:"the new configuration"`
|
||||
Accept string `header:"Accept" default:"application/json" required:"false"`
|
||||
}
|
||||
|
||||
// ConfigurationGetInput is the parameters for HTTP API method to get Configuration.
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
// EventInput is the parameters for the Event HTTP API method.
|
||||
type EventInput struct {
|
||||
Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"false"`
|
||||
Accept string `header:"Accept" default:"application/nostr+json;q=0.9,application/x-realy-event:q=0.1"`
|
||||
RawBody []byte
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/danielgtaylor/huma/v2"
|
||||
"github.com/rickb777/acceptable/header"
|
||||
|
||||
"realy.lol/chk"
|
||||
"realy.lol/context"
|
||||
@@ -20,8 +21,13 @@ import (
|
||||
|
||||
// EventsInput is the parameters for an Events HTTP API method. Basically an array of eventid.T.
|
||||
type EventsInput struct {
|
||||
Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"false"`
|
||||
Body []string `doc:"list of event Ids"`
|
||||
Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"false"`
|
||||
Accept string `header:"Accept" default:"application/nostr+json;q=0.9,application/x-realy-event;q=0.1" doc:"event encoding format that is expected, priority using mimetype;q=0.x will indicate preference when multiple are available"`
|
||||
Body []string `doc:"list of event Ids"`
|
||||
}
|
||||
|
||||
type EventsOutput struct {
|
||||
Limit int `header:"X-Limit" default:"1000" doc:"informs client maximum number of events that they can request"`
|
||||
}
|
||||
|
||||
// RegisterEvents is the implementation of the HTTP API for Events.
|
||||
@@ -32,15 +38,14 @@ func (x *Operations) RegisterEvents(api huma.API) {
|
||||
scopes := []string{"user", "read"}
|
||||
method := http.MethodPost
|
||||
huma.Register(api, huma.Operation{
|
||||
OperationID: name,
|
||||
Summary: name,
|
||||
Path: path,
|
||||
Method: method,
|
||||
Tags: []string{"events"},
|
||||
Description: helpers.GenerateDescription(description, scopes),
|
||||
Security: []map[string][]string{{"auth": scopes}},
|
||||
DefaultStatus: 204,
|
||||
}, func(ctx context.T, input *EventsInput) (output *huma.StreamResponse, err error) {
|
||||
OperationID: name,
|
||||
Summary: name,
|
||||
Path: path,
|
||||
Method: method,
|
||||
Tags: []string{"events"},
|
||||
Description: helpers.GenerateDescription(description, scopes),
|
||||
Security: []map[string][]string{{"auth": scopes}},
|
||||
}, func(ctx context.T, input *EventsInput) (output *struct{}, err error) {
|
||||
// log.I.S(input)
|
||||
if len(input.Body) == 10000 {
|
||||
err = huma.Error400BadRequest(
|
||||
@@ -52,6 +57,10 @@ func (x *Operations) RegisterEvents(api huma.API) {
|
||||
if len(input.Body) > 1000 || x.Server.AuthRequired() {
|
||||
authrequired = true
|
||||
}
|
||||
limit := 1000
|
||||
if !authrequired {
|
||||
limit = 10000
|
||||
}
|
||||
r := ctx.Value("http-request").(*http.Request)
|
||||
var valid bool
|
||||
var pubkey []byte
|
||||
@@ -97,14 +106,27 @@ func (x *Operations) RegisterEvents(api huma.API) {
|
||||
}
|
||||
evIds = append(evIds, idb)
|
||||
}
|
||||
|
||||
if idsWriter, ok := sto.(store.GetIdsWriter); ok {
|
||||
output = &huma.StreamResponse{
|
||||
func(ctx huma.Context) {
|
||||
if err = idsWriter.FetchIds(x.Context(), tag.New(evIds...),
|
||||
ctx.BodyWriter()); chk.E(err) {
|
||||
return
|
||||
}
|
||||
},
|
||||
w := ctx.Value("http-response").(http.ResponseWriter)
|
||||
var binary bool
|
||||
precedence := header.ParsePrecedenceValues(r.Header.Get("Accept"))
|
||||
done:
|
||||
for _, v := range precedence {
|
||||
switch v.Value {
|
||||
case "application/x-realy-event":
|
||||
binary = true
|
||||
break done
|
||||
case "application/nostr+json":
|
||||
break done
|
||||
default:
|
||||
break done
|
||||
}
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("X-Limit", fmt.Sprint(limit))
|
||||
if err = idsWriter.FetchIds(w, x.Context(), tag.New(evIds...), binary); chk.E(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package ratel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
|
||||
"realy.lol/chk"
|
||||
"realy.lol/context"
|
||||
"realy.lol/event"
|
||||
"realy.lol/ratel/keys/id"
|
||||
"realy.lol/ratel/keys/serial"
|
||||
"realy.lol/ratel/prefixes"
|
||||
@@ -14,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// FetchIds retrieves events based on a list of event Ids that have been provided.
|
||||
func (r *T) FetchIds(c context.T, evIds *tag.T, out io.Writer) (err error) {
|
||||
func (r *T) FetchIds(w io.Writer, c context.T, evIds *tag.T, binary bool) (err error) {
|
||||
b := make([]byte, 0, 100000)
|
||||
err = r.View(func(txn *badger.Txn) (err error) {
|
||||
for _, v := range evIds.ToSliceOfBytes() {
|
||||
@@ -38,11 +40,30 @@ func (r *T) FetchIds(c context.T, evIds *tag.T, out io.Writer) (err error) {
|
||||
if b, err = item.ValueCopy(nil); chk.E(err) {
|
||||
return
|
||||
}
|
||||
if _, err = out.Write(b); chk.E(err) {
|
||||
if binary {
|
||||
if !r.Binary {
|
||||
ev := event.New()
|
||||
if b, err = ev.Unmarshal(b); chk.E(err) {
|
||||
return
|
||||
}
|
||||
ev.MarshalBinary(w)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if r.Binary {
|
||||
ev := event.New()
|
||||
buf := bytes.NewBuffer(b)
|
||||
if err = ev.UnmarshalBinary(buf); chk.E(err) {
|
||||
return
|
||||
}
|
||||
b = ev.Marshal(nil)
|
||||
}
|
||||
}
|
||||
if _, err = w.Write(b); chk.E(err) {
|
||||
return
|
||||
}
|
||||
// add the new line after entries
|
||||
if _, err = out.Write([]byte{'\n'}); chk.E(err) {
|
||||
if _, err = w.Write([]byte{'\n'}); chk.E(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ type Querier interface {
|
||||
}
|
||||
|
||||
type GetIdsWriter interface {
|
||||
FetchIds(c context.T, evIds *tag.T, out io.Writer) (err error)
|
||||
FetchIds(w io.Writer, c context.T, evIds *tag.T, binary bool) (err error)
|
||||
}
|
||||
|
||||
type Deleter interface {
|
||||
|
||||
Reference in New Issue
Block a user