forked from tangled.org/core
this repo has no description

knotserver: add handler to retrieve retrieve repo language percentage

authored by brookjeynes.dev and committed by anirudh.fi 72cefcdf fca4c5e7

Changed files
+154 -2
knotserver
types
+1 -1
flake.nix
··· 49 49 inherit (gitignore.lib) gitignoreSource; 50 50 in { 51 51 overlays.default = final: prev: let 52 - goModHash = "sha256-SfyLSQa3g30PXCi/VQOm0cz372B6YdXE7xfBlhgCGec="; 52 + goModHash = "sha256-EooM036KFlO4Zot5vDLX+HFU2GfjCtjVEfF7t+d4Avk="; 53 53 buildCmdPackage = name: 54 54 final.buildGoModule { 55 55 pname = name;
+3 -1
go.mod
··· 16 16 github.com/dustin/go-humanize v1.0.1 17 17 github.com/gliderlabs/ssh v0.3.8 18 18 github.com/go-chi/chi/v5 v5.2.0 19 - github.com/go-git/go-git/v5 v5.0.0-00010101000000-000000000000 19 + github.com/go-enry/go-enry/v2 v2.9.2 20 + github.com/go-git/go-git/v5 v5.14.0 20 21 github.com/google/uuid v1.6.0 21 22 github.com/gorilla/sessions v1.4.0 22 23 github.com/haileyok/atproto-oauth-golang v0.0.2 ··· 49 50 github.com/dlclark/regexp2 v1.11.5 // indirect 50 51 github.com/emirpasic/gods v1.18.1 // indirect 51 52 github.com/felixge/httpsnoop v1.0.4 // indirect 53 + github.com/go-enry/go-oniguruma v1.2.1 // indirect 52 54 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 53 55 github.com/go-git/go-billy/v5 v5.6.2 // indirect 54 56 github.com/go-logr/logr v1.4.2 // indirect
+5
go.sum
··· 69 69 github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= 70 70 github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0= 71 71 github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 72 + github.com/go-enry/go-enry/v2 v2.9.2 h1:giOQAtCgBX08kosrX818DCQJTCNtKwoPBGu0qb6nKTY= 73 + github.com/go-enry/go-enry/v2 v2.9.2/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= 74 + github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= 75 + github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= 72 76 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 73 77 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 74 78 github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= ··· 263 267 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 264 268 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 265 269 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 270 + github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 266 271 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 267 272 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 268 273 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+36
knotserver/git/git.go
··· 169 169 return c, nil 170 170 } 171 171 172 + func (g *GitRepo) FileContentN(path string, cap int64) ([]byte, error) { 173 + buf := []byte{} 174 + 175 + c, err := g.r.CommitObject(g.h) 176 + if err != nil { 177 + return nil, fmt.Errorf("commit object: %w", err) 178 + } 179 + 180 + tree, err := c.Tree() 181 + if err != nil { 182 + return nil, fmt.Errorf("file tree: %w", err) 183 + } 184 + 185 + file, err := tree.File(path) 186 + if err != nil { 187 + return nil, err 188 + } 189 + 190 + isbin, _ := file.IsBinary() 191 + 192 + if !isbin { 193 + reader, err := file.Reader() 194 + if err != nil { 195 + return nil, err 196 + } 197 + bufReader := io.LimitReader(reader, cap) 198 + _, err = bufReader.Read(buf) 199 + if err != nil { 200 + return nil, err 201 + } 202 + return buf, nil 203 + } else { 204 + return nil, ErrBinaryFile 205 + } 206 + } 207 + 172 208 func (g *GitRepo) FileContent(path string) (string, error) { 173 209 c, err := g.r.CommitObject(g.h) 174 210 if err != nil {
+1
knotserver/handler.go
··· 126 126 r.Route("/repo", func(r chi.Router) { 127 127 r.Use(h.VerifySignature) 128 128 r.Put("/new", h.NewRepo) 129 + r.Get("/languages/{branch}", h.RepoLanguages) 129 130 r.Delete("/", h.RemoveRepo) 130 131 r.Route("/fork", func(r chi.Router) { 131 132 r.Post("/", h.RepoFork)
+102
knotserver/routes.go
··· 12 12 "net/http" 13 13 "net/url" 14 14 "os" 15 + "path" 15 16 "path/filepath" 16 17 "strconv" 17 18 "strings" ··· 19 20 securejoin "github.com/cyphar/filepath-securejoin" 20 21 "github.com/gliderlabs/ssh" 21 22 "github.com/go-chi/chi/v5" 23 + "github.com/go-enry/go-enry/v2" 22 24 gogit "github.com/go-git/go-git/v5" 23 25 "github.com/go-git/go-git/v5/plumbing" 24 26 "github.com/go-git/go-git/v5/plumbing/object" ··· 710 712 711 713 w.Header().Set("Content-Type", "application/json") 712 714 json.NewEncoder(w).Encode(types.AncestorCheckResponse{Status: status}) 715 + } 716 + 717 + func (h *Handle) RepoLanguages(w http.ResponseWriter, r *http.Request) { 718 + l := h.l.With("handler", "RepoForkSync") 719 + 720 + data := struct { 721 + Did string `json:"did"` 722 + Source string `json:"source"` 723 + Name string `json:"name,omitempty"` 724 + }{} 725 + 726 + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 727 + writeError(w, "invalid request body", http.StatusBadRequest) 728 + return 729 + } 730 + 731 + did := data.Did 732 + source := data.Source 733 + 734 + if did == "" || source == "" { 735 + l.Error("invalid request body, empty did or name") 736 + w.WriteHeader(http.StatusBadRequest) 737 + return 738 + } 739 + 740 + var name string 741 + if data.Name != "" { 742 + name = data.Name 743 + } else { 744 + name = filepath.Base(source) 745 + } 746 + 747 + branch := chi.URLParam(r, "branch") 748 + branch, _ = url.PathUnescape(branch) 749 + 750 + relativeRepoPath := filepath.Join(did, name) 751 + repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 752 + 753 + gr, err := git.Open(repoPath, branch) 754 + if err != nil { 755 + log.Println(err) 756 + notFound(w) 757 + return 758 + } 759 + 760 + languageFileCount := make(map[string]int) 761 + languagePercentage := make(map[string]float64) 762 + 763 + err = recurseEntireTree(gr, func(absPath string) { 764 + lang, safe := enry.GetLanguageByExtension(absPath) 765 + if len(lang) == 0 || !safe { 766 + content, _ := gr.FileContentN(absPath, 1024) 767 + if !safe { 768 + lang = enry.GetLanguage(absPath, content) 769 + } else { 770 + lang, _ = enry.GetLanguageByContent(absPath, content) 771 + if len(lang) == 0 { 772 + return 773 + } 774 + } 775 + } 776 + 777 + v, ok := languageFileCount[lang] 778 + if ok { 779 + languageFileCount[lang] = v + 1 780 + } else { 781 + languageFileCount[lang] = 1 782 + } 783 + }, "") 784 + if err != nil { 785 + log.Println(err) 786 + writeError(w, err.Error(), http.StatusNoContent) 787 + return 788 + } 789 + 790 + for path, fileCount := range languageFileCount { 791 + percentage := float64(fileCount) / float64(len(languageFileCount)) * 100.0 792 + languagePercentage[path] = percentage 793 + } 794 + 795 + w.Header().Set("Content-Type", "application/json") 796 + json.NewEncoder(w).Encode(types.RepoLanguageResponse{Languages: languagePercentage}) 797 + } 798 + 799 + func recurseEntireTree(git *git.GitRepo, callback func(absPath string), filePath string) error { 800 + files, err := git.FileTree(filePath) 801 + if err != nil { 802 + log.Println(err) 803 + return err 804 + } 805 + 806 + for _, file := range files { 807 + absPath := path.Join(filePath, file.Name) 808 + if !file.IsFile { 809 + return recurseEntireTree(git, callback, absPath) 810 + } 811 + callback(absPath) 812 + } 813 + 814 + return nil 713 815 } 714 816 715 817 func (h *Handle) RepoForkSync(w http.ResponseWriter, r *http.Request) {
+5
types/repo.go
··· 109 109 type AncestorCheckResponse struct { 110 110 Status ForkStatus `json:"status"` 111 111 } 112 + 113 + type RepoLanguageResponse struct { 114 + // Language: Percentage 115 + Languages map[string]float64 `json:"languages"` 116 + }
+1
types/tree.go
··· 8 8 9 9 // A nicer git tree representation. 10 10 type NiceTree struct { 11 + // Relative path 11 12 Name string `json:"name"` 12 13 Mode string `json:"mode"` 13 14 Size int64 `json:"size"`