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-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // 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/savsgio/gotils v0.0.0-20250408102913-196191ec6287 // indirect
|
||||||
github.com/templexxx/cpu v0.1.1 // indirect
|
github.com/templexxx/cpu v0.1.1 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // 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/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 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
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 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
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.
|
// ConfigurationSetInput is the parameters for HTTP API method to set Configuration.
|
||||||
type ConfigurationSetInput struct {
|
type ConfigurationSetInput struct {
|
||||||
Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"true"`
|
Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"true"`
|
||||||
Body config.C `doc:"the new configuration"`
|
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.
|
// 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.
|
// EventInput is the parameters for the Event HTTP API method.
|
||||||
type EventInput struct {
|
type EventInput struct {
|
||||||
Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"false"`
|
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
|
RawBody []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/danielgtaylor/huma/v2"
|
"github.com/danielgtaylor/huma/v2"
|
||||||
|
"github.com/rickb777/acceptable/header"
|
||||||
|
|
||||||
"realy.lol/chk"
|
"realy.lol/chk"
|
||||||
"realy.lol/context"
|
"realy.lol/context"
|
||||||
@@ -20,8 +21,13 @@ import (
|
|||||||
|
|
||||||
// EventsInput is the parameters for an Events HTTP API method. Basically an array of eventid.T.
|
// EventsInput is the parameters for an Events HTTP API method. Basically an array of eventid.T.
|
||||||
type EventsInput struct {
|
type EventsInput struct {
|
||||||
Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"false"`
|
Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"false"`
|
||||||
Body []string `doc:"list of event Ids"`
|
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.
|
// 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"}
|
scopes := []string{"user", "read"}
|
||||||
method := http.MethodPost
|
method := http.MethodPost
|
||||||
huma.Register(api, huma.Operation{
|
huma.Register(api, huma.Operation{
|
||||||
OperationID: name,
|
OperationID: name,
|
||||||
Summary: name,
|
Summary: name,
|
||||||
Path: path,
|
Path: path,
|
||||||
Method: method,
|
Method: method,
|
||||||
Tags: []string{"events"},
|
Tags: []string{"events"},
|
||||||
Description: helpers.GenerateDescription(description, scopes),
|
Description: helpers.GenerateDescription(description, scopes),
|
||||||
Security: []map[string][]string{{"auth": scopes}},
|
Security: []map[string][]string{{"auth": scopes}},
|
||||||
DefaultStatus: 204,
|
}, func(ctx context.T, input *EventsInput) (output *struct{}, err error) {
|
||||||
}, func(ctx context.T, input *EventsInput) (output *huma.StreamResponse, err error) {
|
|
||||||
// log.I.S(input)
|
// log.I.S(input)
|
||||||
if len(input.Body) == 10000 {
|
if len(input.Body) == 10000 {
|
||||||
err = huma.Error400BadRequest(
|
err = huma.Error400BadRequest(
|
||||||
@@ -52,6 +57,10 @@ func (x *Operations) RegisterEvents(api huma.API) {
|
|||||||
if len(input.Body) > 1000 || x.Server.AuthRequired() {
|
if len(input.Body) > 1000 || x.Server.AuthRequired() {
|
||||||
authrequired = true
|
authrequired = true
|
||||||
}
|
}
|
||||||
|
limit := 1000
|
||||||
|
if !authrequired {
|
||||||
|
limit = 10000
|
||||||
|
}
|
||||||
r := ctx.Value("http-request").(*http.Request)
|
r := ctx.Value("http-request").(*http.Request)
|
||||||
var valid bool
|
var valid bool
|
||||||
var pubkey []byte
|
var pubkey []byte
|
||||||
@@ -97,14 +106,27 @@ func (x *Operations) RegisterEvents(api huma.API) {
|
|||||||
}
|
}
|
||||||
evIds = append(evIds, idb)
|
evIds = append(evIds, idb)
|
||||||
}
|
}
|
||||||
|
|
||||||
if idsWriter, ok := sto.(store.GetIdsWriter); ok {
|
if idsWriter, ok := sto.(store.GetIdsWriter); ok {
|
||||||
output = &huma.StreamResponse{
|
w := ctx.Value("http-response").(http.ResponseWriter)
|
||||||
func(ctx huma.Context) {
|
var binary bool
|
||||||
if err = idsWriter.FetchIds(x.Context(), tag.New(evIds...),
|
precedence := header.ParsePrecedenceValues(r.Header.Get("Accept"))
|
||||||
ctx.BodyWriter()); chk.E(err) {
|
done:
|
||||||
return
|
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
|
return
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package ratel
|
package ratel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v4"
|
"github.com/dgraph-io/badger/v4"
|
||||||
|
|
||||||
"realy.lol/chk"
|
"realy.lol/chk"
|
||||||
"realy.lol/context"
|
"realy.lol/context"
|
||||||
|
"realy.lol/event"
|
||||||
"realy.lol/ratel/keys/id"
|
"realy.lol/ratel/keys/id"
|
||||||
"realy.lol/ratel/keys/serial"
|
"realy.lol/ratel/keys/serial"
|
||||||
"realy.lol/ratel/prefixes"
|
"realy.lol/ratel/prefixes"
|
||||||
@@ -14,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// FetchIds retrieves events based on a list of event Ids that have been provided.
|
// 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)
|
b := make([]byte, 0, 100000)
|
||||||
err = r.View(func(txn *badger.Txn) (err error) {
|
err = r.View(func(txn *badger.Txn) (err error) {
|
||||||
for _, v := range evIds.ToSliceOfBytes() {
|
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) {
|
if b, err = item.ValueCopy(nil); chk.E(err) {
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
// add the new line after entries
|
// 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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ type Querier interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetIdsWriter 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 {
|
type Deleter interface {
|
||||||
|
|||||||
Reference in New Issue
Block a user