AuraRepo > Tree [f8c6941]

/context.go/

..
View Raw
package aurarepo

import (
	"bufio"
	"errors"
	"fmt"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing/format/diff"
	sis "gitlab.com/sis-suite/smallnetinformationservices"
	"gitlab.com/sis-suite/smallnetinformationservices/bitset"
)

type AuraRepoType uint

const (
	AuraRepoType_Git AuraRepoType = iota
	AuraRepoType_Prism
)

// The features enabled on the application server
type AuraRepoFeature uint

const (
	AuraRepoFeature_Users_Groups AuraRepoFeature = iota
	AuraRepoFeature_Registration
	AuraRepoFeature_Private_Internal
	AuraRepoFeature_Releases
	AuraRepoFeature_Issues
	AuraRepoFeature_PRs
	AuraRepoFeature_Projects
	AuraRepoFeature_Forks
	AuraRepoFeature_Docs
	AuraRepoFeature_AutoMirror
	AuraRepoFeature_LFS
	AuraRepoFeature_Wiki
	AuraRepoFeature_Pages
	AuraRepoFeature_Email
	AuraRepoFeature_Misfin
)

type AuraRepoContext struct {
	title             string
	repos             map[string]*Repo
	repos_directory   string
	http_clone_prefix string
	//repo             *git.Repository // TODO: Set config values, like committer/user (for when repo is modified in interface).
	enabled_features bitset.BitSet[AuraRepoFeature, AuraRepoFeature]
}

type Repo struct {
	*git.Repository
	Type        AuraRepoType
	Id          string // Could be just repo name, or username/repo_name in multi-user system
	Title       string
	Path        string // Relative to AuraRepoContext.repos_directory
	Description string
}

func (repo *Repo) EscapedId() string {
	return url.PathEscape(repo.Id)
}

func NewAuraRepoContext(title string, directory string, http_clone_prefix string) *AuraRepoContext {
	context := &AuraRepoContext{title: title, repos_directory: directory}
	context.repos = make(map[string]*Repo)
	/*var err error
	context.repo, err = git.PlainOpen("./test-repo/")
	if errors.Is(err, git.ErrRepositoryNotExists) {
		context.repo, err = git.PlainInit("./test-repo/", true)
	}*/

	/*c := config.NewConfig()
	c.Core.IsBare = true
	c.Init.DefaultBranch = "main"
	c.Branches["main"] = &config.Branch{Name: "main"}
	context.repo.SetConfig(c)*/

	return context
}

// Given path is relative to repos directory.
func (c *AuraRepoContext) AddRepo(t AuraRepoType, id string, title string, path string, description string) (*Repo, error) {
	var gitRepo *git.Repository = nil
	if t == AuraRepoType_Git {
		var err error
		gitRepo, err = git.PlainOpen(filepath.Join(c.repos_directory, path))
		if errors.Is(err, git.ErrRepositoryNotExists) {
			gitRepo, err = git.PlainInit(filepath.Join(c.repos_directory, path), true)
			if err != nil {
				return nil, err
			}
		}
	}

	r := &Repo{Repository: gitRepo, Type: t, Title: title, Id: id, Path: path, Description: description}
	c.repos[id] = r
	return r, nil
}

func (c *AuraRepoContext) Attach(s sis.ServeMux) {
	s.AddRoute("/", c.Homepage)

	repoGroup := s.Group("/:reponame/")
	repoGroup.AddRoute("/", func(request *sis.Request) {
		if !strings.HasSuffix(request.Path(), "/") {
			request.Redirect("/")
			return
		}

		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoHomepage(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoHomepage(request, repo)
		}
	})
	repoGroup.AddRoute("/branches/", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoBranches(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoBranches(request, repo)
		}
	})
	repoGroup.AddRoute("/tags/", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoTags(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoTags(request, repo)
		}
	})

	repoGroup.AddRoute("/commit/:ref", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoCommitDetails(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoCommitDetails(request, repo)
		}
	})
	repoGroup.AddRoute("/commits/*", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoCommitsRedirect(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoCommitsRedirect(request, repo)
		}
	})
	repoGroup.AddRoute("/commits/:ref/*", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoCommitsList(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoCommitsList(request, repo)
		}
	})

	repoGroup.AddRoute("/notes/", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoNotespaces(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoNotespaces(request, repo)
		}
	})
	repoGroup.AddRoute("/notes/:name", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoNotespaceNotes(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoNotespaceNotes(request, repo)
		}
	})

	repoGroup.AddRoute("/tree/:ref/*", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoTree(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoTree(request, repo)
		}
	})
	repoGroup.AddRoute("/blob/:ref/*", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoBlob(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoBlob(request, repo)
		}
	})
	repoGroup.AddRoute("/blame/:ref/*", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoBlame(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoBlame(request, repo)
		}
	})
	repoGroup.AddRoute("/raw/:ref/*", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoRaw(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoRaw(request, repo)
		}
	})

	repoGroup.AddRoute("/archive/refs/heads/:name", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoArchiveHead(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoArchiveHead(request, repo)
		}
	})
	repoGroup.AddRoute("/archive/refs/tags/:name", func(request *sis.Request) {
		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoArchiveTag(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoArchiveTag(request, repo)
		}
	})

	repoGroup.AddRoute("/wiki/*", func(request *sis.Request) {
		if strings.HasSuffix(request.Path(), "wiki") {
			request.Redirect("wiki/")
			return
		}

		repo := c.repos[request.GetParam("reponame")]
		if repo.Type == AuraRepoType_Git {
			c.GitRepoWiki(request, repo)
		} else if repo.Type == AuraRepoType_Prism {
			c.PrismRepoWiki(request, repo)
		}
	})
}

func (c *AuraRepoContext) Homepage(request *sis.Request) {
	request.Gemini(fmt.Sprintf("# %s\n\n", c.title))

	// List all repos
	for _, repo := range c.repos {
		request.Link("/"+repo.EscapedId()+"/", repo.Title)
		request.Gemini(fmt.Sprintf("> %s\n", repo.Description))
		request.Gemini("\n")
	}
}

func GetLastFiveLinesOfString(content string) string {
	if len(content) < 100 { // For small contents, keep original approach
		return content
	} else {
		lastNewlines := 0
		i := len(content) - 1
		startPos := i

		// Scan backwards until we find 5 newlines
		for i >= 0 && lastNewlines < 6 {
			if content[i] == '\n' {
				lastNewlines++
				if lastNewlines == 6 {
					startPos = i + 1
					break
				}
			}
			i--
		}

		if lastNewlines < 6 {
			return content
		} else {
			return content[startPos:]
		}
	}
}

func GetFirstFiveLinesOfString(content string) string {
	if len(content) < 100 { // For small contents, return all
		return content
	} else {
		nextNewlines := 0
		i := 0
		endPos := len(content) - 1

		// Scan forwards until we find 5 newlines
		for i < len(content) && nextNewlines < 5 {
			if content[i] == '\n' {
				nextNewlines++
				if nextNewlines == 5 {
					endPos = i + 1
					break
				}
			}
			i++
		}

		if nextNewlines < 5 {
			return content
		} else {
			return content[:endPos]
		}
	}
}

func PrintIndentedLinesFromNumber(request *sis.Request, content string, startLine int, operation diff.Operation) {
	prefix := " "
	if operation == diff.Add {
		prefix = "+"
	} else if operation == diff.Delete {
		prefix = "-"
	}

	scanner := bufio.NewScanner(strings.NewReader(content))
	lineNum := startLine
	for scanner.Scan() {
		request.PlainText("%s%5s | %s\n", prefix, strconv.Itoa(lineNum), scanner.Text())
		lineNum++
	}
}