feat(search): detect languages with go-enry and gitattributes overrides
111 files changed, +540 −342
- .gitattributes added +3 −0
- account.go → forge/account.go renamed +1 −1
- admin.go → forge/admin.go renamed +1 −1
- attach.go → forge/attach.go renamed +1 −1
- avatar.go → forge/avatar.go renamed +1 −1
- backup.go → forge/backup.go renamed +1 −1
- forge/cli.go added +251 −0
- edit.go → forge/edit.go renamed +1 −1
- extra.go → forge/extra.go renamed +1 −1
- flow_test.go → forge/flow_test.go renamed +1 −1
- git.go → forge/git.go renamed +1 −1
- highlight.go → forge/highlight.go renamed +1 −1
- inbox.go → forge/inbox.go renamed +1 −1
- issues.go → forge/issues.go renamed +1 −1
- labels.go → forge/labels.go renamed +1 −1
- limit.go → forge/limit.go renamed +1 −1
- mail.go → forge/mail.go renamed +1 −1
- mail_test.go → forge/mail_test.go renamed +1 −1
- pull.go → forge/pull.go renamed +1 −1
- quota.go → forge/quota.go renamed +1 −1
- search.go → forge/search.go renamed +208 −53
- shell.go → forge/shell.go renamed +1 −1
- smart.go → forge/smart.go renamed +1 −1
- store.go → forge/store.go renamed +1 −1
- totp.go → forge/totp.go renamed +1 −1
- unit_test.go → forge/unit_test.go renamed +1 −1
- web.go → forge/web.go renamed +12 −12
- go.mod modified +7 −5
- go.sum modified +21 −0
- main.go modified +2 −246
- static/crop.js → web/static/crop.js renamed +0 −0
- static/editor.js → web/static/editor.js renamed +0 −0
- static/fonts/sans-400-italic.woff2 → web/static/fonts/sans-400-italic.woff2 renamed +0 −0
- static/fonts/sans-400-normal.woff2 → web/static/fonts/sans-400-normal.woff2 renamed +0 −0
- static/fonts/sans-600-normal.woff2 → web/static/fonts/sans-600-normal.woff2 renamed +0 −0
- static/fonts/sans-700-normal.woff2 → web/static/fonts/sans-700-normal.woff2 renamed +0 −0
- static/fonts/sans.txt → web/static/fonts/sans.txt renamed +0 −0
- static/fonts/variable.woff2 → web/static/fonts/variable.woff2 renamed +0 −0
- static/icon.png → web/static/icon.png renamed +0 −0
- static/katex/LICENSE.txt → web/static/katex/LICENSE.txt renamed +0 −0
- static/katex/auto-render.min.js → web/static/katex/auto-render.min.js renamed +0 −0
- static/katex/fonts/KaTeX_AMS-Regular.woff2 → web/static/katex/fonts/KaTeX_AMS-Regular.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Caligraphic-Bold.woff2 → web/static/katex/fonts/KaTeX_Caligraphic-Bold.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Caligraphic-Regular.woff2 → web/static/katex/fonts/KaTeX_Caligraphic-Regular.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Fraktur-Bold.woff2 → web/static/katex/fonts/KaTeX_Fraktur-Bold.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Fraktur-Regular.woff2 → web/static/katex/fonts/KaTeX_Fraktur-Regular.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Main-Bold.woff2 → web/static/katex/fonts/KaTeX_Main-Bold.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Main-BoldItalic.woff2 → web/static/katex/fonts/KaTeX_Main-BoldItalic.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Main-Italic.woff2 → web/static/katex/fonts/KaTeX_Main-Italic.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Main-Regular.woff2 → web/static/katex/fonts/KaTeX_Main-Regular.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Math-BoldItalic.woff2 → web/static/katex/fonts/KaTeX_Math-BoldItalic.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Math-Italic.woff2 → web/static/katex/fonts/KaTeX_Math-Italic.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_SansSerif-Bold.woff2 → web/static/katex/fonts/KaTeX_SansSerif-Bold.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_SansSerif-Italic.woff2 → web/static/katex/fonts/KaTeX_SansSerif-Italic.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_SansSerif-Regular.woff2 → web/static/katex/fonts/KaTeX_SansSerif-Regular.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Script-Regular.woff2 → web/static/katex/fonts/KaTeX_Script-Regular.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Size1-Regular.woff2 → web/static/katex/fonts/KaTeX_Size1-Regular.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Size2-Regular.woff2 → web/static/katex/fonts/KaTeX_Size2-Regular.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Size3-Regular.woff2 → web/static/katex/fonts/KaTeX_Size3-Regular.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Size4-Regular.woff2 → web/static/katex/fonts/KaTeX_Size4-Regular.woff2 renamed +0 −0
- static/katex/fonts/KaTeX_Typewriter-Regular.woff2 → web/static/katex/fonts/KaTeX_Typewriter-Regular.woff2 renamed +0 −0
- static/katex/katex.min.css → web/static/katex/katex.min.css renamed +0 −0
- static/katex/katex.min.js → web/static/katex/katex.min.js renamed +0 −0
- static/logo.png → web/static/logo.png renamed +0 −0
- static/math.js → web/static/math.js renamed +0 −0
- static/style.css → web/static/style.css renamed +3 −2
- templates/admin.html → web/templates/admin.tmpl renamed +0 −0
- templates/appearance.html → web/templates/appearance.tmpl renamed +0 −0
- templates/blame.html → web/templates/blame.tmpl renamed +0 −0
- templates/blob.html → web/templates/blob.tmpl renamed +0 −0
- templates/commit.html → web/templates/commit.tmpl renamed +0 −0
- templates/compare.html → web/templates/compare.tmpl renamed +0 −0
- templates/create.html → web/templates/create.tmpl renamed +0 −0
- templates/edit.html → web/templates/edit.tmpl renamed +0 −0
- templates/error.html → web/templates/error.tmpl renamed +0 −0
- templates/index.html → web/templates/index.tmpl renamed +0 −0
- templates/interdiff.html → web/templates/interdiff.tmpl renamed +0 −0
- templates/issue.html → web/templates/issue.tmpl renamed +0 −0
- templates/issues.html → web/templates/issues.tmpl renamed +0 −0
- templates/keys.html → web/templates/keys.tmpl renamed +0 −0
- templates/layout.html → web/templates/layout.tmpl renamed +0 −0
- templates/log.html → web/templates/log.tmpl renamed +0 −0
- templates/login.html → web/templates/login.tmpl renamed +0 −0
- templates/message.html → web/templates/message.tmpl renamed +0 −0
- templates/meta.html → web/templates/meta.tmpl renamed +0 −0
- templates/newissue.html → web/templates/newissue.tmpl renamed +0 −0
- templates/neworg.html → web/templates/neworg.tmpl renamed +0 −0
- templates/newpull.html → web/templates/newpull.tmpl renamed +0 −0
- templates/notifications.html → web/templates/notifications.tmpl renamed +0 −0
- templates/org.html → web/templates/org.tmpl renamed +0 −0
- templates/orgsettings.html → web/templates/orgsettings.tmpl renamed +0 −0
- templates/pull.html → web/templates/pull.tmpl renamed +0 −0
- templates/pulls.html → web/templates/pulls.tmpl renamed +0 −0
- templates/recovery.html → web/templates/recovery.tmpl renamed +0 −0
- templates/refs.html → web/templates/refs.tmpl renamed +0 −0
- templates/register.html → web/templates/register.tmpl renamed +0 −0
- templates/repo.html → web/templates/repo.tmpl renamed +1 −1
- templates/repos.html → web/templates/repos.tmpl renamed +0 −0
- templates/reposettings.html → web/templates/reposettings.tmpl renamed +0 −0
- templates/reset.html → web/templates/reset.tmpl renamed +0 −0
- templates/resetpassword.html → web/templates/resetpassword.tmpl renamed +0 −0
- templates/search.html → web/templates/search.tmpl renamed +0 −0
- templates/security.html → web/templates/security.tmpl renamed +0 −0
- templates/settings.html → web/templates/settings.tmpl renamed +0 −0
- templates/stars.html → web/templates/stars.tmpl renamed +0 −0
- templates/tokens.html → web/templates/tokens.tmpl renamed +0 −0
- templates/totp.html → web/templates/totp.tmpl renamed +0 −0
- templates/totpsetup.html → web/templates/totpsetup.tmpl renamed +0 −0
- templates/tree.html → web/templates/tree.tmpl renamed +0 −0
- templates/user.html → web/templates/user.tmpl renamed +0 −0
- web/web.go added +9 −0
.gitattributes added
| @@ -0,0 +1,3 @@ | ||
| 1 | +*.tmpl linguist-detectable=false | |
| 2 | +*.css linguist-detectable | |
| 3 | +web/static/katex/** linguist-vendored | |
account.go → forge/account.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "encoding/base64" |
admin.go → forge/admin.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "errors" |
attach.go → forge/attach.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "bytes" |
avatar.go → forge/avatar.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "bytes" |
backup.go → forge/backup.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "context" |
forge/cli.go added
| @@ -0,0 +1,251 @@ | ||
| 1 | +package forge | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "bufio" | |
| 5 | + "flag" | |
| 6 | + "fmt" | |
| 7 | + "log" | |
| 8 | + "net" | |
| 9 | + "net/http" | |
| 10 | + "os" | |
| 11 | + "path/filepath" | |
| 12 | + "strconv" | |
| 13 | + "strings" | |
| 14 | + "time" | |
| 15 | +) | |
| 16 | + | |
| 17 | +func Main() { | |
| 18 | + root := flag.String("root", "/srv/gitpocket", "data directory holding gitpocket.db and repos/") | |
| 19 | + flag.Usage = func() { | |
| 20 | + fmt.Fprintln(os.Stderr, `usage: gitpocket [-root dir] command | |
| 21 | + | |
| 22 | + serve [-listen addr] [-host name] [-name text] [-ssh-user name] [-proxy cidr,...] run the web server | |
| 23 | + keys user home type key sshd AuthorizedKeysCommand | |
| 24 | + shell user forced command for ssh git access | |
| 25 | + invite print a single use registration code | |
| 26 | + registration [open|invite|closed] show or set who may register | |
| 27 | + admin name | unadmin name grant or revoke site administration | |
| 28 | + quota [size] show or set the per user disk quota, 0 disables | |
| 29 | + user name create a user, password read from stdin | |
| 30 | + passwd name set a user's password, read from stdin | |
| 31 | + ban name | unban name block or restore an account | |
| 32 | + delete name remove an account and its repositories | |
| 33 | + feature owner/repo... | -owner/repo set or clear the featured projects, in order | |
| 34 | + org name [+user|-user]... create an organization and add or remove members | |
| 35 | + mail address send a test message through the configured mailer | |
| 36 | + backup dir | file.db snapshot the database and bundle every changed repository into dir`) | |
| 37 | + } | |
| 38 | + flag.Parse() | |
| 39 | + args := flag.Args() | |
| 40 | + if len(args) == 0 { | |
| 41 | + flag.Usage() | |
| 42 | + os.Exit(2) | |
| 43 | + } | |
| 44 | + switch args[0] { | |
| 45 | + case "serve": | |
| 46 | + serve(*root, args[1:]) | |
| 47 | + case "keys": | |
| 48 | + binary, _ := os.Executable() | |
| 49 | + os.Exit(keys(*root, binary, args[1:])) | |
| 50 | + case "hook": | |
| 51 | + if len(args) != 3 { | |
| 52 | + os.Exit(2) | |
| 53 | + } | |
| 54 | + os.Exit(hook(*root, args[1], args[2])) | |
| 55 | + case "shell": | |
| 56 | + if len(args) != 2 { | |
| 57 | + os.Exit(2) | |
| 58 | + } | |
| 59 | + os.Exit(shell(*root, args[1])) | |
| 60 | + case "quota": | |
| 61 | + if len(args) == 2 { | |
| 62 | + n, err := parseSize(args[1]) | |
| 63 | + if err != nil { | |
| 64 | + log.Fatal(err) | |
| 65 | + } | |
| 66 | + if err := writeQuota(*root, n); err != nil { | |
| 67 | + log.Fatal(err) | |
| 68 | + } | |
| 69 | + } | |
| 70 | + if q := readQuota(*root); q == 0 { | |
| 71 | + fmt.Println("no quota") | |
| 72 | + } else { | |
| 73 | + fmt.Println(humanSize(q)) | |
| 74 | + } | |
| 75 | + case "registration": | |
| 76 | + store := mustStore(*root) | |
| 77 | + if len(args) == 2 { | |
| 78 | + if !validRegistration(args[1]) { | |
| 79 | + log.Fatal("registration is open, invite or closed") | |
| 80 | + } | |
| 81 | + if err := store.SetSetting("registration", args[1]); err != nil { | |
| 82 | + log.Fatal(err) | |
| 83 | + } | |
| 84 | + } | |
| 85 | + fmt.Println(store.Registration()) | |
| 86 | + case "admin", "unadmin": | |
| 87 | + if len(args) != 2 { | |
| 88 | + os.Exit(2) | |
| 89 | + } | |
| 90 | + if err := mustStore(*root).SetAdmin(args[1], args[0] == "admin"); err != nil { | |
| 91 | + log.Fatal("no such user") | |
| 92 | + } | |
| 93 | + case "invite": | |
| 94 | + store := mustStore(*root) | |
| 95 | + code, err := store.CreateInvite() | |
| 96 | + if err != nil { | |
| 97 | + log.Fatal(err) | |
| 98 | + } | |
| 99 | + fmt.Println(code) | |
| 100 | + case "user": | |
| 101 | + if len(args) != 2 { | |
| 102 | + os.Exit(2) | |
| 103 | + } | |
| 104 | + store := mustStore(*root) | |
| 105 | + password := readLine() | |
| 106 | + if _, err := store.CreateUser(args[1], password); err != nil { | |
| 107 | + log.Fatal(err) | |
| 108 | + } | |
| 109 | + case "passwd": | |
| 110 | + if len(args) != 2 { | |
| 111 | + os.Exit(2) | |
| 112 | + } | |
| 113 | + store := mustStore(*root) | |
| 114 | + u, err := store.UserByName(args[1]) | |
| 115 | + if err != nil { | |
| 116 | + log.Fatal("no such user") | |
| 117 | + } | |
| 118 | + password := readLine() | |
| 119 | + if err := store.SetPassword(u.ID, password); err != nil { | |
| 120 | + log.Fatal(err) | |
| 121 | + } | |
| 122 | + store.DeleteSessions(u.ID) | |
| 123 | + case "backup": | |
| 124 | + if len(args) != 2 { | |
| 125 | + os.Exit(2) | |
| 126 | + } | |
| 127 | + if err := backup(*root, args[1], 14); err != nil { | |
| 128 | + log.Fatal(err) | |
| 129 | + } | |
| 130 | + case "mail": | |
| 131 | + if len(args) != 2 { | |
| 132 | + os.Exit(2) | |
| 133 | + } | |
| 134 | + if err := loadMailer(*root).Send(args[1], "test message from gitpocket", "Mail delivery from this forge works.\n"); err != nil { | |
| 135 | + log.Fatal(err) | |
| 136 | + } | |
| 137 | + fmt.Println("sent") | |
| 138 | + case "org": | |
| 139 | + if len(args) < 2 { | |
| 140 | + os.Exit(2) | |
| 141 | + } | |
| 142 | + store := mustStore(*root) | |
| 143 | + org, err := store.UserByName(args[1]) | |
| 144 | + if err != nil { | |
| 145 | + if org, err = store.CreateOrg(args[1]); err != nil { | |
| 146 | + log.Fatal(err) | |
| 147 | + } | |
| 148 | + } else if !store.IsOrg(org.ID) { | |
| 149 | + log.Fatalf("~%s is a user, not an organization", args[1]) | |
| 150 | + } | |
| 151 | + for _, spec := range args[2:] { | |
| 152 | + name := strings.TrimPrefix(strings.TrimLeft(spec, "+-"), "~") | |
| 153 | + u, err := store.UserByName(name) | |
| 154 | + if err != nil || store.IsOrg(u.ID) { | |
| 155 | + log.Fatalf("%s: no such user", spec) | |
| 156 | + } | |
| 157 | + if err := store.SetMember(org.ID, u.ID, !strings.HasPrefix(spec, "-")); err != nil { | |
| 158 | + log.Fatal(err) | |
| 159 | + } | |
| 160 | + } | |
| 161 | + case "feature": | |
| 162 | + if len(args) < 2 { | |
| 163 | + os.Exit(2) | |
| 164 | + } | |
| 165 | + store := mustStore(*root) | |
| 166 | + for i, spec := range args[1:] { | |
| 167 | + owner, name, ok := strings.Cut(strings.TrimPrefix(spec, "~"), "/") | |
| 168 | + rank := i + 1 | |
| 169 | + if strings.HasPrefix(spec, "-") { | |
| 170 | + owner, name, ok = strings.Cut(strings.TrimPrefix(spec, "-~"), "/") | |
| 171 | + rank = 0 | |
| 172 | + } | |
| 173 | + if !ok { | |
| 174 | + log.Fatalf("%s: expected owner/repo", spec) | |
| 175 | + } | |
| 176 | + if err := store.SetFeatured(owner, name, rank); err != nil { | |
| 177 | + log.Fatalf("%s: no such repository", spec) | |
| 178 | + } | |
| 179 | + } | |
| 180 | + case "ban", "unban": | |
| 181 | + if len(args) != 2 { | |
| 182 | + os.Exit(2) | |
| 183 | + } | |
| 184 | + if err := mustStore(*root).SetBanned(args[1], args[0] == "ban"); err != nil { | |
| 185 | + log.Fatal(err) | |
| 186 | + } | |
| 187 | + case "delete": | |
| 188 | + if len(args) != 2 { | |
| 189 | + os.Exit(2) | |
| 190 | + } | |
| 191 | + store := mustStore(*root) | |
| 192 | + u, err := store.UserByName(args[1]) | |
| 193 | + if err != nil { | |
| 194 | + log.Fatal("no such user") | |
| 195 | + } | |
| 196 | + if err := store.DeleteUser(args[1]); err != nil { | |
| 197 | + log.Fatal(err) | |
| 198 | + } | |
| 199 | + os.RemoveAll(filepath.Join(*root, "repos", args[1])) | |
| 200 | + os.Remove(filepath.Join(*root, "avatars", strconv.FormatInt(u.ID, 10))) | |
| 201 | + default: | |
| 202 | + flag.Usage() | |
| 203 | + os.Exit(2) | |
| 204 | + } | |
| 205 | +} | |
| 206 | + | |
| 207 | +func mustStore(root string) *Store { | |
| 208 | + if err := os.MkdirAll(filepath.Join(root, "repos"), 0o755); err != nil { | |
| 209 | + log.Fatal(err) | |
| 210 | + } | |
| 211 | + store, err := openStore(filepath.Join(root, "gitpocket.db"), false) | |
| 212 | + if err != nil { | |
| 213 | + log.Fatal(err) | |
| 214 | + } | |
| 215 | + return store | |
| 216 | +} | |
| 217 | + | |
| 218 | +func serve(root string, args []string) { | |
| 219 | + fs := flag.NewFlagSet("serve", flag.ExitOnError) | |
| 220 | + listen := fs.String("listen", "127.0.0.1:3200", "address to listen on") | |
| 221 | + host := fs.String("host", "localhost", "public hostname used in clone urls") | |
| 222 | + name := fs.String("name", "gitpocket", "site name shown in the header") | |
| 223 | + sshUser := fs.String("ssh-user", "git", "unix user that serves git over ssh, shown in clone urls") | |
| 224 | + proxies := fs.String("proxy", "", "comma separated networks besides loopback whose X-Forwarded-For is trusted") | |
| 225 | + fs.Parse(args) | |
| 226 | + for _, cidr := range strings.Split(*proxies, ",") { | |
| 227 | + if cidr = strings.TrimSpace(cidr); cidr == "" { | |
| 228 | + continue | |
| 229 | + } | |
| 230 | + _, n, err := net.ParseCIDR(cidr) | |
| 231 | + if err != nil { | |
| 232 | + log.Fatalf("-proxy %s: %v", cidr, err) | |
| 233 | + } | |
| 234 | + trustedProxies = append(trustedProxies, n) | |
| 235 | + } | |
| 236 | + store := mustStore(root) | |
| 237 | + srv := &http.Server{ | |
| 238 | + Addr: *listen, | |
| 239 | + Handler: newServer(store, root, *host, *name, *sshUser).handler(), | |
| 240 | + ReadHeaderTimeout: 10 * time.Second, | |
| 241 | + IdleTimeout: 2 * time.Minute, | |
| 242 | + MaxHeaderBytes: 64 << 10, | |
| 243 | + } | |
| 244 | + log.Printf("gitpocket listening on %s for %s", *listen, *host) | |
| 245 | + log.Fatal(srv.ListenAndServe()) | |
| 246 | +} | |
| 247 | + | |
| 248 | +func readLine() string { | |
| 249 | + line, _ := bufio.NewReader(os.Stdin).ReadString('\n') | |
| 250 | + return strings.TrimRight(line, "\r\n") | |
| 251 | +} | |
edit.go → forge/edit.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "bufio" |
extra.go → forge/extra.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "compress/gzip" |
flow_test.go → forge/flow_test.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "bytes" |
git.go → forge/git.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "bytes" |
highlight.go → forge/highlight.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "bytes" |
inbox.go → forge/inbox.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "fmt" |
issues.go → forge/issues.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "database/sql" |
labels.go → forge/labels.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "errors" |
limit.go → forge/limit.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "net" |
mail.go → forge/mail.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "bytes" |
mail_test.go → forge/mail_test.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "encoding/json" |
pull.go → forge/pull.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "database/sql" |
quota.go → forge/quota.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "errors" |
search.go → forge/search.go renamed
| @@ -1,6 +1,7 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | + "bytes" | |
| 4 | 5 | "net/http" |
| 5 | 6 | "path" |
| 6 | 7 | "slices" |
| @@ -8,6 +9,8 @@ import ( | ||
| 8 | 9 | "strings" |
| 9 | 10 | "sync" |
| 10 | 11 | "time" |
| 12 | + | |
| 13 | + "github.com/go-enry/go-enry/v2" | |
| 11 | 14 | ) |
| 12 | 15 | |
| 13 | 16 | type language struct { |
| @@ -29,7 +32,7 @@ var languages = map[string][2]string{ | ||
| 29 | 32 | ".ex": {"Elixir", "#6e4a7e"}, ".exs": {"Elixir", "#6e4a7e"}, ".erl": {"Erlang", "#b83998"}, ".gleam": {"Gleam", "#ffaff3"}, |
| 30 | 33 | ".lua": {"Lua", "#000080"}, ".nix": {"Nix", "#7e7eff"}, ".sh": {"Shell", "#89e051"}, ".tex": {"TeX", "#3d6117"}, |
| 31 | 34 | ".html": {"HTML", "#e34c26"}, ".css": {"CSS", "#663399"}, ".scm": {"Scheme", "#1e4aec"}, ".rkt": {"Racket", "#3c5caa"}, |
| 32 | - ".lisp": {"Common Lisp", "#3fb68b"}, ".clj": {"Clojure", "#db5855"}, ".fs": {"F#", "#b845fc"}, ".nim": {"Nim", "#ffc200"}, ".mm": {"Objective-C++", "#6866fb"}, | |
| 35 | + ".lisp": {"Common Lisp", "#3fb68b"}, ".clj": {"Clojure", "#db5855"}, ".fs": {"F#", "#b845fc"}, ".nim": {"Nim", "#ffc200"}, ".tmpl": {"Go Template", "#5a9fb5"}, ".gotmpl": {"Go Template", "#5a9fb5"}, ".gohtml": {"Go Template", "#5a9fb5"}, ".mm": {"Objective-C++", "#6866fb"}, | |
| 33 | 36 | ".fst": {"F*", "#572e30"}, ".fsti": {"F*", "#572e30"}, ".dfy": {"Dafny", "#ffec25"}, ".tla": {"TLA+", "#4b0079"}, ".als": {"Alloy", "#64c800"}, |
| 34 | 37 | ".mlw": {"WhyML", "#9e3b3b"}, ".bpl": {"Boogie", "#c80fa0"}, ".vpr": {"Viper", "#7d5c99"}, ".pvs": {"PVS", "#7a5a3b"}, ".pml": {"Promela", "#de0000"}, |
| 35 | 38 | ".smv": {"NuSMV", "#5c6bc0"}, ".sy": {"SyGuS", "#7b8d9a"}, ".sl": {"SyGuS", "#7b8d9a"}, ".smt": {"SMT", "#7b8d9a"}, ".z3": {"SMT", "#7b8d9a"}, |
| @@ -62,21 +65,6 @@ var languages = map[string][2]string{ | ||
| 62 | 65 | ".sig": {"Standard ML", "#dc566d"}, ".fun": {"Standard ML", "#dc566d"}, ".fut": {"Futhark", "#5f021f"}, ".u": {"Unison", "#2f4f4f"}, |
| 63 | 66 | } |
| 64 | 67 | |
| 65 | -var vendorDirs = []string{"vendor/", "node_modules/", "third_party/", "thirdparty/", "external/", "deps/", "dist/", "katex/"} | |
| 66 | - | |
| 67 | -func vendored(name string) bool { | |
| 68 | - base := path.Base(name) | |
| 69 | - if strings.Contains(base, ".min.") || strings.HasSuffix(base, ".pb.go") || strings.HasSuffix(base, "_generated.go") { | |
| 70 | - return true | |
| 71 | - } | |
| 72 | - for _, d := range vendorDirs { | |
| 73 | - if strings.HasPrefix(name, d) || strings.Contains(name, "/"+d) { | |
| 74 | - return true | |
| 75 | - } | |
| 76 | - } | |
| 77 | - return false | |
| 78 | -} | |
| 79 | - | |
| 80 | 68 | type langEntry struct { |
| 81 | 69 | head string |
| 82 | 70 | stamp time.Time |
| @@ -108,42 +96,7 @@ func (s *Server) repoLanguages(repo Repo) []language { | ||
| 108 | 96 | langCache.Unlock() |
| 109 | 97 | return e.langs |
| 110 | 98 | } |
| 111 | - out, err := g.run(nil, "ls-tree", "-r", "-l", "-z", head) | |
| 112 | - if err != nil { | |
| 113 | - return nil | |
| 114 | - } | |
| 115 | - bytes := map[string]int64{} | |
| 116 | - var total int64 | |
| 117 | - for _, rec := range strings.Split(string(out), "\x00") { | |
| 118 | - meta, name, ok := strings.Cut(rec, "\t") | |
| 119 | - f := strings.Fields(meta) | |
| 120 | - if !ok || len(f) != 4 || f[1] != "blob" || vendored(name) { | |
| 121 | - continue | |
| 122 | - } | |
| 123 | - l, known := languages[path.Ext(name)] | |
| 124 | - if !known { | |
| 125 | - l, known = languages[strings.ToLower(path.Ext(name))] | |
| 126 | - } | |
| 127 | - if !known { | |
| 128 | - continue | |
| 129 | - } | |
| 130 | - size, _ := strconv.ParseInt(f[3], 10, 64) | |
| 131 | - bytes[l[0]] += size | |
| 132 | - total += size | |
| 133 | - } | |
| 134 | - var langs []language | |
| 135 | - for _, l := range languages { | |
| 136 | - if n, ok := bytes[l[0]]; ok { | |
| 137 | - langs = append(langs, language{Name: l[0], Colour: l[1], Bytes: n, Percent: float64(n) * 100 / float64(total)}) | |
| 138 | - delete(bytes, l[0]) | |
| 139 | - } | |
| 140 | - } | |
| 141 | - slices.SortFunc(langs, func(a, b language) int { | |
| 142 | - if a.Bytes != b.Bytes { | |
| 143 | - return int(b.Bytes - a.Bytes) | |
| 144 | - } | |
| 145 | - return strings.Compare(a.Name, b.Name) | |
| 146 | - }) | |
| 99 | + langs := g.languages(head) | |
| 147 | 100 | langCache.Lock() |
| 148 | 101 | langCache.m[repo.ID] = langEntry{head, stamp, langs} |
| 149 | 102 | langCache.Unlock() |
| @@ -409,3 +362,205 @@ func (s *Server) people() []userHit { | ||
| 409 | 362 | peopleCache.hits, peopleCache.at = hits, time.Now() |
| 410 | 363 | return hits |
| 411 | 364 | } |
| 365 | + | |
| 366 | +type blob struct { | |
| 367 | + name, id string | |
| 368 | + size int64 | |
| 369 | + lang string | |
| 370 | + guesses []string | |
| 371 | +} | |
| 372 | + | |
| 373 | +func (g Git) languages(head string) []language { | |
| 374 | + out, err := g.run(nil, "ls-tree", "-r", "-l", "-z", head) | |
| 375 | + if err != nil { | |
| 376 | + return nil | |
| 377 | + } | |
| 378 | + var blobs []blob | |
| 379 | + attributes := false | |
| 380 | + for _, rec := range strings.Split(string(out), "\x00") { | |
| 381 | + meta, name, ok := strings.Cut(rec, "\t") | |
| 382 | + f := strings.Fields(meta) | |
| 383 | + if !ok || len(f) != 4 || f[1] != "blob" { | |
| 384 | + continue | |
| 385 | + } | |
| 386 | + size, _ := strconv.ParseInt(f[3], 10, 64) | |
| 387 | + if size > 0 { | |
| 388 | + blobs = append(blobs, blob{name: name, id: f[2], size: size}) | |
| 389 | + } | |
| 390 | + attributes = attributes || path.Base(name) == ".gitattributes" | |
| 391 | + } | |
| 392 | + var attrs map[string]map[string]string | |
| 393 | + if attributes { | |
| 394 | + attrs = g.linguist(head, blobs) | |
| 395 | + } | |
| 396 | + var queued []*blob | |
| 397 | + var budget int64 = 8 << 20 | |
| 398 | + kept := blobs[:0] | |
| 399 | + for _, b := range blobs { | |
| 400 | + a := attrs[b.name] | |
| 401 | + if skip(a["linguist-vendored"], enry.IsVendor(b.name)) || skip(a["linguist-generated"], enry.IsGenerated(b.name, nil)) || | |
| 402 | + skip(a["linguist-documentation"], enry.IsDocumentation(b.name)) || | |
| 403 | + a["linguist-detectable"] != "set" && (enry.IsDotFile(b.name) || enry.IsConfiguration(b.name)) { | |
| 404 | + continue | |
| 405 | + } | |
| 406 | + if v := a["linguist-language"]; v != "" && v != "set" && v != "unset" { | |
| 407 | + b.lang, _ = enry.GetLanguageByAlias(v) | |
| 408 | + } | |
| 409 | + if b.lang == "" { | |
| 410 | + b.guesses = enry.GetLanguagesByFilename(b.name, nil, nil) | |
| 411 | + if len(b.guesses) == 0 { | |
| 412 | + b.guesses = enry.GetLanguagesByExtension(b.name, nil, nil) | |
| 413 | + } | |
| 414 | + } | |
| 415 | + kept = append(kept, b) | |
| 416 | + } | |
| 417 | + for i := range kept { | |
| 418 | + b := &kept[i] | |
| 419 | + if len(b.guesses) > 1 && b.size <= 256<<10 && budget >= b.size { | |
| 420 | + budget -= b.size | |
| 421 | + queued = append(queued, b) | |
| 422 | + } | |
| 423 | + } | |
| 424 | + if len(queued) > 0 { | |
| 425 | + var ids strings.Builder | |
| 426 | + for _, b := range queued { | |
| 427 | + ids.WriteString(b.id + "\n") | |
| 428 | + } | |
| 429 | + out, _ := g.run([]byte(ids.String()), "cat-file", "--batch") | |
| 430 | + for _, b := range queued { | |
| 431 | + header, rest, ok := bytes.Cut(out, []byte("\n")) | |
| 432 | + f := strings.Fields(string(header)) | |
| 433 | + if !ok || len(f) != 3 { | |
| 434 | + break | |
| 435 | + } | |
| 436 | + n, _ := strconv.Atoi(f[2]) | |
| 437 | + if n > len(rest) { | |
| 438 | + break | |
| 439 | + } | |
| 440 | + content := rest[:min(n, 16<<10)] | |
| 441 | + out = rest[min(n+1, len(rest)):] | |
| 442 | + if l := enry.GetLanguage(path.Base(b.name), content); l != "" { | |
| 443 | + b.lang = l | |
| 444 | + } | |
| 445 | + } | |
| 446 | + } | |
| 447 | + votes := map[string]map[string]int{} | |
| 448 | + for _, b := range kept { | |
| 449 | + if len(b.guesses) > 1 && b.lang != "" { | |
| 450 | + ext := strings.ToLower(path.Ext(b.name)) | |
| 451 | + if votes[ext] == nil { | |
| 452 | + votes[ext] = map[string]int{} | |
| 453 | + } | |
| 454 | + votes[ext][b.lang]++ | |
| 455 | + } | |
| 456 | + } | |
| 457 | + for i := range kept { | |
| 458 | + b := &kept[i] | |
| 459 | + if len(b.guesses) <= 1 { | |
| 460 | + continue | |
| 461 | + } | |
| 462 | + best, n := "", 0 | |
| 463 | + for lang, c := range votes[strings.ToLower(path.Ext(b.name))] { | |
| 464 | + if c > n || c == n && lang < best { | |
| 465 | + best, n = lang, c | |
| 466 | + } | |
| 467 | + } | |
| 468 | + if best != "" { | |
| 469 | + b.lang = best | |
| 470 | + } | |
| 471 | + } | |
| 472 | + sizes := map[string]int64{} | |
| 473 | + colours := map[string]string{} | |
| 474 | + var total int64 | |
| 475 | + for _, b := range kept { | |
| 476 | + lang, colour, ok := classify(b, attrs[b.name]["linguist-detectable"]) | |
| 477 | + if !ok { | |
| 478 | + continue | |
| 479 | + } | |
| 480 | + sizes[lang] += b.size | |
| 481 | + colours[lang] = colour | |
| 482 | + total += b.size | |
| 483 | + } | |
| 484 | + var langs []language | |
| 485 | + for name, n := range sizes { | |
| 486 | + langs = append(langs, language{Name: name, Colour: colours[name], Bytes: n, Percent: float64(n) * 100 / float64(total)}) | |
| 487 | + } | |
| 488 | + slices.SortFunc(langs, func(a, b language) int { | |
| 489 | + if a.Bytes != b.Bytes { | |
| 490 | + return int(b.Bytes - a.Bytes) | |
| 491 | + } | |
| 492 | + return strings.Compare(a.Name, b.Name) | |
| 493 | + }) | |
| 494 | + return langs | |
| 495 | +} | |
| 496 | + | |
| 497 | +func skip(attr string, guess bool) bool { | |
| 498 | + if attr == "set" || attr == "true" { | |
| 499 | + return true | |
| 500 | + } | |
| 501 | + if attr == "unset" || attr == "false" { | |
| 502 | + return false | |
| 503 | + } | |
| 504 | + return guess | |
| 505 | +} | |
| 506 | + | |
| 507 | +func classify(b blob, detectable string) (string, string, bool) { | |
| 508 | + lang := b.lang | |
| 509 | + if lang == "" && len(b.guesses) > 0 { | |
| 510 | + lang = b.guesses[0] | |
| 511 | + } | |
| 512 | + if lang == "" { | |
| 513 | + l, ok := languages[strings.ToLower(path.Ext(b.name))] | |
| 514 | + return l[0], l[1], ok && detectable != "unset" && detectable != "false" | |
| 515 | + } | |
| 516 | + if group := enry.GetLanguageGroup(lang); group != "" { | |
| 517 | + lang = group | |
| 518 | + } | |
| 519 | + switch detectable { | |
| 520 | + case "set", "true": | |
| 521 | + case "unset", "false": | |
| 522 | + return "", "", false | |
| 523 | + default: | |
| 524 | + if enry.GetLanguageType(lang) != enry.Programming { | |
| 525 | + return "", "", false | |
| 526 | + } | |
| 527 | + } | |
| 528 | + return lang, enry.GetColor(lang), true | |
| 529 | +} | |
| 530 | + | |
| 531 | +func (g Git) linguist(head string, blobs []blob) map[string]map[string]string { | |
| 532 | + var names strings.Builder | |
| 533 | + for _, b := range blobs { | |
| 534 | + names.WriteString(b.name + "\x00") | |
| 535 | + } | |
| 536 | + out, err := g.run([]byte(names.String()), "check-attr", "--source="+head, "-z", "--stdin", | |
| 537 | + "linguist-vendored", "linguist-generated", "linguist-documentation", "linguist-detectable", "linguist-language") | |
| 538 | + if err != nil { | |
| 539 | + return nil | |
| 540 | + } | |
| 541 | + attrs := map[string]map[string]string{} | |
| 542 | + f := strings.Split(string(out), "\x00") | |
| 543 | + for i := 0; i+2 < len(f); i += 3 { | |
| 544 | + if f[i+2] == "unspecified" { | |
| 545 | + continue | |
| 546 | + } | |
| 547 | + if attrs[f[i]] == nil { | |
| 548 | + attrs[f[i]] = map[string]string{} | |
| 549 | + } | |
| 550 | + attrs[f[i]][f[i+1]] = f[i+2] | |
| 551 | + } | |
| 552 | + return attrs | |
| 553 | +} | |
| 554 | + | |
| 555 | +func topLanguages(langs []language, limit int) []language { | |
| 556 | + if len(langs) <= limit+1 { | |
| 557 | + return langs | |
| 558 | + } | |
| 559 | + top := slices.Clone(langs[:limit]) | |
| 560 | + other := language{Name: "Other", Colour: "#cccccc"} | |
| 561 | + for _, l := range langs[limit:] { | |
| 562 | + other.Bytes += l.Bytes | |
| 563 | + other.Percent += l.Percent | |
| 564 | + } | |
| 565 | + return append(top, other) | |
| 566 | +} | |
shell.go → forge/shell.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "fmt" |
smart.go → forge/smart.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "compress/gzip" |
store.go → forge/store.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "crypto/rand" |
totp.go → forge/totp.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "crypto/hmac" |
unit_test.go → forge/unit_test.go renamed
| @@ -1,4 +1,4 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "net" |
web.go → forge/web.go renamed
| @@ -1,9 +1,8 @@ | ||
| 1 | -package main | |
| 1 | +package forge | |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | 4 | "bytes" |
| 5 | 5 | "crypto/sha256" |
| 6 | - "embed" | |
| 7 | 6 | "encoding/hex" |
| 8 | 7 | "errors" |
| 9 | 8 | "fmt" |
| @@ -33,13 +32,14 @@ import ( | ||
| 33 | 32 | highlighting "github.com/yuin/goldmark-highlighting/v2" |
| 34 | 33 | "github.com/yuin/goldmark/extension" |
| 35 | 34 | gmhtml "github.com/yuin/goldmark/renderer/html" |
| 36 | -) | |
| 37 | 35 | |
| 38 | -//go:embed templates/*.html | |
| 39 | -var templateFS embed.FS | |
| 36 | + "gitpocket/web" | |
| 37 | +) | |
| 40 | 38 | |
| 41 | -//go:embed static | |
| 42 | -var staticFS embed.FS | |
| 39 | +var ( | |
| 40 | + templateFS = web.Templates | |
| 41 | + staticFS = web.Static | |
| 42 | +) | |
| 43 | 43 | |
| 44 | 44 | type Server struct { |
| 45 | 45 | mailer *Mailer |
| @@ -128,16 +128,16 @@ func newServer(store *Store, root, host, name string, sshUser string) *Server { | ||
| 128 | 128 | }, |
| 129 | 129 | "join": func(parts []string, upto int) string { return strings.Join(parts[:upto], "/") }, |
| 130 | 130 | } |
| 131 | - names, _ := fs.Glob(templateFS, "templates/*.html") | |
| 131 | + names, _ := fs.Glob(templateFS, "templates/*.tmpl") | |
| 132 | 132 | for _, name := range names { |
| 133 | 133 | base := filepath.Base(name) |
| 134 | - if base == "layout.html" { | |
| 134 | + if base == "layout.tmpl" { | |
| 135 | 135 | continue |
| 136 | 136 | } |
| 137 | - if base == "meta.html" { | |
| 137 | + if base == "meta.tmpl" { | |
| 138 | 138 | continue |
| 139 | 139 | } |
| 140 | - s.pages[strings.TrimSuffix(base, ".html")] = template.Must(template.New("layout.html").Funcs(funcs).ParseFS(templateFS, "templates/layout.html", "templates/meta.html", name)) | |
| 140 | + s.pages[strings.TrimSuffix(base, ".tmpl")] = template.Must(template.New("layout.tmpl").Funcs(funcs).ParseFS(templateFS, "templates/layout.tmpl", "templates/meta.tmpl", name)) | |
| 141 | 141 | } |
| 142 | 142 | s.markdown = goldmark.New(goldmark.WithExtensions(extension.GFM, mathjax.MathJax, |
| 143 | 143 | highlighting.NewHighlighting(highlighting.WithGuessLanguage(true), highlighting.WithFormatOptions(chromahtml.WithClasses(true)))), |
| @@ -1130,7 +1130,7 @@ func (s *Server) summary(w http.ResponseWriter, r *http.Request, repo Repo) { | ||
| 1130 | 1130 | if !rp.Empty { |
| 1131 | 1131 | sum := s.summaryData(repo, g, rp) |
| 1132 | 1132 | data["Commits"], data["Branches"], data["Tags"] = sum.commits, sum.branches, sum.tags |
| 1133 | - data["Languages"] = s.repoLanguages(repo) | |
| 1133 | + data["Languages"] = topLanguages(s.repoLanguages(repo), 6) | |
| 1134 | 1134 | if sum.readmeName != "" { |
| 1135 | 1135 | data["ReadmeName"] = sum.readmeName |
| 1136 | 1136 | if sum.readme != "" { |
go.mod modified
| @@ -3,26 +3,28 @@ module gitpocket | ||
| 3 | 3 | go 1.27.1 |
| 4 | 4 | |
| 5 | 5 | require ( |
| 6 | + github.com/alecthomas/chroma/v2 v2.27.0 | |
| 7 | + github.com/go-enry/go-enry/v2 v2.9.6 | |
| 8 | + github.com/litao91/goldmark-mathjax v0.0.0-20210217064022-a43cf739a50f | |
| 6 | 9 | github.com/microcosm-cc/bluemonday v1.0.27 |
| 10 | + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e | |
| 7 | 11 | github.com/yuin/goldmark v1.8.7 |
| 12 | + github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc | |
| 8 | 13 | golang.org/x/crypto v0.59.0 |
| 14 | + golang.org/x/image v0.48.0 | |
| 9 | 15 | modernc.org/sqlite v1.62.0 |
| 10 | 16 | ) |
| 11 | 17 | |
| 12 | 18 | require ( |
| 13 | - github.com/alecthomas/chroma/v2 v2.27.0 // indirect | |
| 14 | 19 | github.com/aymerick/douceur v0.2.0 // indirect |
| 15 | 20 | github.com/dlclark/regexp2/v2 v2.2.1 // indirect |
| 16 | 21 | github.com/dustin/go-humanize v1.1.0 // indirect |
| 22 | + github.com/go-enry/go-oniguruma v1.2.1 // indirect | |
| 17 | 23 | github.com/google/uuid v1.6.0 // indirect |
| 18 | 24 | github.com/gorilla/css v1.0.1 // indirect |
| 19 | - github.com/litao91/goldmark-mathjax v0.0.0-20210217064022-a43cf739a50f // indirect | |
| 20 | 25 | github.com/mattn/go-isatty v0.0.24 // indirect |
| 21 | 26 | github.com/ncruces/go-strftime v1.1.0 // indirect |
| 22 | 27 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect |
| 23 | - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect | |
| 24 | - github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect | |
| 25 | - golang.org/x/image v0.48.0 // indirect | |
| 26 | 28 | golang.org/x/net v0.62.0 // indirect |
| 27 | 29 | golang.org/x/sys v0.50.0 // indirect |
| 28 | 30 | modernc.org/libc v1.80.1 // indirect |
go.sum modified
| @@ -1,10 +1,15 @@ | ||
| 1 | +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= | |
| 2 | +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= | |
| 1 | 3 | github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= |
| 2 | 4 | github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs= |
| 3 | 5 | github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8= |
| 4 | 6 | github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= |
| 7 | +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= | |
| 8 | +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= | |
| 5 | 9 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= |
| 6 | 10 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= |
| 7 | 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
| 12 | +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |
| 8 | 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
| 9 | 14 | github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= |
| 10 | 15 | github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= |
| @@ -12,6 +17,10 @@ github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee | ||
| 12 | 17 | github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= |
| 13 | 18 | github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2xWg= |
| 14 | 19 | github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI= |
| 20 | +github.com/go-enry/go-enry/v2 v2.9.6 h1:np63eOtMV56zfYDHnFVgpEVOk8fr2kmylcMnAZUDbSs= | |
| 21 | +github.com/go-enry/go-enry/v2 v2.9.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= | |
| 22 | +github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= | |
| 23 | +github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= | |
| 15 | 24 | github.com/google/pprof v0.0.0-20261016150608-1facf8f91b50 h1:PlNWJRxu5APSV0pWqfIsZ0qt9OOGHSWfZQ4zvx+WgWs= |
| 16 | 25 | github.com/google/pprof v0.0.0-20261016150608-1facf8f91b50/go.mod h1:yUo1AMMpsPIDFGuwDK8nLcf8+vvAd5l2O6JDZ6hpwrM= |
| 17 | 26 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= |
| @@ -20,6 +29,8 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= | ||
| 20 | 29 | github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= |
| 21 | 30 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= |
| 22 | 31 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= |
| 32 | +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= | |
| 33 | +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | |
| 23 | 34 | github.com/litao91/goldmark-mathjax v0.0.0-20210217064022-a43cf739a50f h1:plCPYXRXDCO57qjqegCzaVf1t6aSbgCMD+zfz18POfs= |
| 24 | 35 | github.com/litao91/goldmark-mathjax v0.0.0-20210217064022-a43cf739a50f/go.mod h1:leg+HM7jUS84JYuY120zmU68R6+UeU6uZ/KAW7cViKE= |
| 25 | 36 | github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= |
| @@ -28,14 +39,21 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX | ||
| 28 | 39 | github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= |
| 29 | 40 | github.com/ncruces/go-strftime v1.1.0 h1:vtPmPWMkb539RGV9WpWT9EwJGvrOCKvMNI4obWL47Hk= |
| 30 | 41 | github.com/ncruces/go-strftime v1.1.0/go.mod h1:yVaq7iwCN09vHRrKBrrflXT0M9tTMG2KmZ9Fj7xeG/g= |
| 42 | +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |
| 31 | 43 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
| 32 | 44 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= |
| 33 | 45 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= |
| 34 | 46 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= |
| 35 | 47 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= |
| 36 | 48 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
| 49 | +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | |
| 50 | +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | |
| 37 | 51 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
| 38 | 52 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
| 53 | +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | |
| 54 | +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | |
| 55 | +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= | |
| 56 | +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | |
| 39 | 57 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= |
| 40 | 58 | github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= |
| 41 | 59 | github.com/yuin/goldmark v1.8.7 h1:XT/tNqCIbabh0ln675w4x3dv04kQBKi8NwYqi9IJaxI= |
| @@ -59,7 +77,10 @@ golang.org/x/term v0.48.0/go.mod h1:BRDLHCZr2SUjIeBHHpRyQeC9xzSAVbzY2rvKgafnHf4= | ||
| 59 | 77 | golang.org/x/tools v0.52.0 h1:ryxg4U63oaUYk7uOLTF37n43f4qgtGvqV4Ioit1BaEs= |
| 60 | 78 | golang.org/x/tools v0.52.0/go.mod h1:ItcP5UNaVGG+Yc4eIYmaFLNJ4soEopkx5Y2d4lcEurs= |
| 61 | 79 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
| 80 | +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |
| 62 | 81 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
| 82 | +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | |
| 83 | +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | |
| 63 | 84 | modernc.org/cc/v4 v4.30.6-0.20261101114202-1b9d1a076f6b h1:WE5SLOURcNd8s6uPR/n4WV4weQxxYKi5OpEZkfNVvdE= |
| 64 | 85 | modernc.org/cc/v4 v4.30.6-0.20261101114202-1b9d1a076f6b/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= |
| 65 | 86 | modernc.org/ccgo/v4 v4.38.1 h1:wdxJVBuFT8JufZnnQyS6Bbz3NOWhViD0xJOGR96Bpd4= |
main.go modified
| @@ -1,251 +1,7 @@ | ||
| 1 | 1 | package main |
| 2 | 2 | |
| 3 | -import ( | |
| 4 | - "bufio" | |
| 5 | - "flag" | |
| 6 | - "fmt" | |
| 7 | - "log" | |
| 8 | - "net" | |
| 9 | - "net/http" | |
| 10 | - "os" | |
| 11 | - "path/filepath" | |
| 12 | - "strconv" | |
| 13 | - "strings" | |
| 14 | - "time" | |
| 15 | -) | |
| 3 | +import "gitpocket/forge" | |
| 16 | 4 | |
| 17 | 5 | func main() { |
| 18 | - root := flag.String("root", "/srv/gitpocket", "data directory holding gitpocket.db and repos/") | |
| 19 | - flag.Usage = func() { | |
| 20 | - fmt.Fprintln(os.Stderr, `usage: gitpocket [-root dir] command | |
| 21 | - | |
| 22 | - serve [-listen addr] [-host name] [-name text] [-ssh-user name] [-proxy cidr,...] run the web server | |
| 23 | - keys user home type key sshd AuthorizedKeysCommand | |
| 24 | - shell user forced command for ssh git access | |
| 25 | - invite print a single use registration code | |
| 26 | - registration [open|invite|closed] show or set who may register | |
| 27 | - admin name | unadmin name grant or revoke site administration | |
| 28 | - quota [size] show or set the per user disk quota, 0 disables | |
| 29 | - user name create a user, password read from stdin | |
| 30 | - passwd name set a user's password, read from stdin | |
| 31 | - ban name | unban name block or restore an account | |
| 32 | - delete name remove an account and its repositories | |
| 33 | - feature owner/repo... | -owner/repo set or clear the featured projects, in order | |
| 34 | - org name [+user|-user]... create an organization and add or remove members | |
| 35 | - mail address send a test message through the configured mailer | |
| 36 | - backup dir | file.db snapshot the database and bundle every changed repository into dir`) | |
| 37 | - } | |
| 38 | - flag.Parse() | |
| 39 | - args := flag.Args() | |
| 40 | - if len(args) == 0 { | |
| 41 | - flag.Usage() | |
| 42 | - os.Exit(2) | |
| 43 | - } | |
| 44 | - switch args[0] { | |
| 45 | - case "serve": | |
| 46 | - serve(*root, args[1:]) | |
| 47 | - case "keys": | |
| 48 | - binary, _ := os.Executable() | |
| 49 | - os.Exit(keys(*root, binary, args[1:])) | |
| 50 | - case "hook": | |
| 51 | - if len(args) != 3 { | |
| 52 | - os.Exit(2) | |
| 53 | - } | |
| 54 | - os.Exit(hook(*root, args[1], args[2])) | |
| 55 | - case "shell": | |
| 56 | - if len(args) != 2 { | |
| 57 | - os.Exit(2) | |
| 58 | - } | |
| 59 | - os.Exit(shell(*root, args[1])) | |
| 60 | - case "quota": | |
| 61 | - if len(args) == 2 { | |
| 62 | - n, err := parseSize(args[1]) | |
| 63 | - if err != nil { | |
| 64 | - log.Fatal(err) | |
| 65 | - } | |
| 66 | - if err := writeQuota(*root, n); err != nil { | |
| 67 | - log.Fatal(err) | |
| 68 | - } | |
| 69 | - } | |
| 70 | - if q := readQuota(*root); q == 0 { | |
| 71 | - fmt.Println("no quota") | |
| 72 | - } else { | |
| 73 | - fmt.Println(humanSize(q)) | |
| 74 | - } | |
| 75 | - case "registration": | |
| 76 | - store := mustStore(*root) | |
| 77 | - if len(args) == 2 { | |
| 78 | - if !validRegistration(args[1]) { | |
| 79 | - log.Fatal("registration is open, invite or closed") | |
| 80 | - } | |
| 81 | - if err := store.SetSetting("registration", args[1]); err != nil { | |
| 82 | - log.Fatal(err) | |
| 83 | - } | |
| 84 | - } | |
| 85 | - fmt.Println(store.Registration()) | |
| 86 | - case "admin", "unadmin": | |
| 87 | - if len(args) != 2 { | |
| 88 | - os.Exit(2) | |
| 89 | - } | |
| 90 | - if err := mustStore(*root).SetAdmin(args[1], args[0] == "admin"); err != nil { | |
| 91 | - log.Fatal("no such user") | |
| 92 | - } | |
| 93 | - case "invite": | |
| 94 | - store := mustStore(*root) | |
| 95 | - code, err := store.CreateInvite() | |
| 96 | - if err != nil { | |
| 97 | - log.Fatal(err) | |
| 98 | - } | |
| 99 | - fmt.Println(code) | |
| 100 | - case "user": | |
| 101 | - if len(args) != 2 { | |
| 102 | - os.Exit(2) | |
| 103 | - } | |
| 104 | - store := mustStore(*root) | |
| 105 | - password := readLine() | |
| 106 | - if _, err := store.CreateUser(args[1], password); err != nil { | |
| 107 | - log.Fatal(err) | |
| 108 | - } | |
| 109 | - case "passwd": | |
| 110 | - if len(args) != 2 { | |
| 111 | - os.Exit(2) | |
| 112 | - } | |
| 113 | - store := mustStore(*root) | |
| 114 | - u, err := store.UserByName(args[1]) | |
| 115 | - if err != nil { | |
| 116 | - log.Fatal("no such user") | |
| 117 | - } | |
| 118 | - password := readLine() | |
| 119 | - if err := store.SetPassword(u.ID, password); err != nil { | |
| 120 | - log.Fatal(err) | |
| 121 | - } | |
| 122 | - store.DeleteSessions(u.ID) | |
| 123 | - case "backup": | |
| 124 | - if len(args) != 2 { | |
| 125 | - os.Exit(2) | |
| 126 | - } | |
| 127 | - if err := backup(*root, args[1], 14); err != nil { | |
| 128 | - log.Fatal(err) | |
| 129 | - } | |
| 130 | - case "mail": | |
| 131 | - if len(args) != 2 { | |
| 132 | - os.Exit(2) | |
| 133 | - } | |
| 134 | - if err := loadMailer(*root).Send(args[1], "test message from gitpocket", "Mail delivery from this forge works.\n"); err != nil { | |
| 135 | - log.Fatal(err) | |
| 136 | - } | |
| 137 | - fmt.Println("sent") | |
| 138 | - case "org": | |
| 139 | - if len(args) < 2 { | |
| 140 | - os.Exit(2) | |
| 141 | - } | |
| 142 | - store := mustStore(*root) | |
| 143 | - org, err := store.UserByName(args[1]) | |
| 144 | - if err != nil { | |
| 145 | - if org, err = store.CreateOrg(args[1]); err != nil { | |
| 146 | - log.Fatal(err) | |
| 147 | - } | |
| 148 | - } else if !store.IsOrg(org.ID) { | |
| 149 | - log.Fatalf("~%s is a user, not an organization", args[1]) | |
| 150 | - } | |
| 151 | - for _, spec := range args[2:] { | |
| 152 | - name := strings.TrimPrefix(strings.TrimLeft(spec, "+-"), "~") | |
| 153 | - u, err := store.UserByName(name) | |
| 154 | - if err != nil || store.IsOrg(u.ID) { | |
| 155 | - log.Fatalf("%s: no such user", spec) | |
| 156 | - } | |
| 157 | - if err := store.SetMember(org.ID, u.ID, !strings.HasPrefix(spec, "-")); err != nil { | |
| 158 | - log.Fatal(err) | |
| 159 | - } | |
| 160 | - } | |
| 161 | - case "feature": | |
| 162 | - if len(args) < 2 { | |
| 163 | - os.Exit(2) | |
| 164 | - } | |
| 165 | - store := mustStore(*root) | |
| 166 | - for i, spec := range args[1:] { | |
| 167 | - owner, name, ok := strings.Cut(strings.TrimPrefix(spec, "~"), "/") | |
| 168 | - rank := i + 1 | |
| 169 | - if strings.HasPrefix(spec, "-") { | |
| 170 | - owner, name, ok = strings.Cut(strings.TrimPrefix(spec, "-~"), "/") | |
| 171 | - rank = 0 | |
| 172 | - } | |
| 173 | - if !ok { | |
| 174 | - log.Fatalf("%s: expected owner/repo", spec) | |
| 175 | - } | |
| 176 | - if err := store.SetFeatured(owner, name, rank); err != nil { | |
| 177 | - log.Fatalf("%s: no such repository", spec) | |
| 178 | - } | |
| 179 | - } | |
| 180 | - case "ban", "unban": | |
| 181 | - if len(args) != 2 { | |
| 182 | - os.Exit(2) | |
| 183 | - } | |
| 184 | - if err := mustStore(*root).SetBanned(args[1], args[0] == "ban"); err != nil { | |
| 185 | - log.Fatal(err) | |
| 186 | - } | |
| 187 | - case "delete": | |
| 188 | - if len(args) != 2 { | |
| 189 | - os.Exit(2) | |
| 190 | - } | |
| 191 | - store := mustStore(*root) | |
| 192 | - u, err := store.UserByName(args[1]) | |
| 193 | - if err != nil { | |
| 194 | - log.Fatal("no such user") | |
| 195 | - } | |
| 196 | - if err := store.DeleteUser(args[1]); err != nil { | |
| 197 | - log.Fatal(err) | |
| 198 | - } | |
| 199 | - os.RemoveAll(filepath.Join(*root, "repos", args[1])) | |
| 200 | - os.Remove(filepath.Join(*root, "avatars", strconv.FormatInt(u.ID, 10))) | |
| 201 | - default: | |
| 202 | - flag.Usage() | |
| 203 | - os.Exit(2) | |
| 204 | - } | |
| 205 | -} | |
| 206 | - | |
| 207 | -func mustStore(root string) *Store { | |
| 208 | - if err := os.MkdirAll(filepath.Join(root, "repos"), 0o755); err != nil { | |
| 209 | - log.Fatal(err) | |
| 210 | - } | |
| 211 | - store, err := openStore(filepath.Join(root, "gitpocket.db"), false) | |
| 212 | - if err != nil { | |
| 213 | - log.Fatal(err) | |
| 214 | - } | |
| 215 | - return store | |
| 216 | -} | |
| 217 | - | |
| 218 | -func serve(root string, args []string) { | |
| 219 | - fs := flag.NewFlagSet("serve", flag.ExitOnError) | |
| 220 | - listen := fs.String("listen", "127.0.0.1:3200", "address to listen on") | |
| 221 | - host := fs.String("host", "localhost", "public hostname used in clone urls") | |
| 222 | - name := fs.String("name", "gitpocket", "site name shown in the header") | |
| 223 | - sshUser := fs.String("ssh-user", "git", "unix user that serves git over ssh, shown in clone urls") | |
| 224 | - proxies := fs.String("proxy", "", "comma separated networks besides loopback whose X-Forwarded-For is trusted") | |
| 225 | - fs.Parse(args) | |
| 226 | - for _, cidr := range strings.Split(*proxies, ",") { | |
| 227 | - if cidr = strings.TrimSpace(cidr); cidr == "" { | |
| 228 | - continue | |
| 229 | - } | |
| 230 | - _, n, err := net.ParseCIDR(cidr) | |
| 231 | - if err != nil { | |
| 232 | - log.Fatalf("-proxy %s: %v", cidr, err) | |
| 233 | - } | |
| 234 | - trustedProxies = append(trustedProxies, n) | |
| 235 | - } | |
| 236 | - store := mustStore(root) | |
| 237 | - srv := &http.Server{ | |
| 238 | - Addr: *listen, | |
| 239 | - Handler: newServer(store, root, *host, *name, *sshUser).handler(), | |
| 240 | - ReadHeaderTimeout: 10 * time.Second, | |
| 241 | - IdleTimeout: 2 * time.Minute, | |
| 242 | - MaxHeaderBytes: 64 << 10, | |
| 243 | - } | |
| 244 | - log.Printf("gitpocket listening on %s for %s", *listen, *host) | |
| 245 | - log.Fatal(srv.ListenAndServe()) | |
| 246 | -} | |
| 247 | - | |
| 248 | -func readLine() string { | |
| 249 | - line, _ := bufio.NewReader(os.Stdin).ReadString('\n') | |
| 250 | - return strings.TrimRight(line, "\r\n") | |
| 6 | + forge.Main() | |
| 251 | 7 | } |
static/crop.js → web/static/crop.js renamed
static/editor.js → web/static/editor.js renamed
static/fonts/sans-400-italic.woff2 → web/static/fonts/sans-400-italic.woff2 renamed
static/fonts/sans-400-normal.woff2 → web/static/fonts/sans-400-normal.woff2 renamed
static/fonts/sans-600-normal.woff2 → web/static/fonts/sans-600-normal.woff2 renamed
static/fonts/sans-700-normal.woff2 → web/static/fonts/sans-700-normal.woff2 renamed
static/fonts/sans.txt → web/static/fonts/sans.txt renamed
static/fonts/variable.woff2 → web/static/fonts/variable.woff2 renamed
static/icon.png → web/static/icon.png renamed
static/katex/LICENSE.txt → web/static/katex/LICENSE.txt renamed
static/katex/auto-render.min.js → web/static/katex/auto-render.min.js renamed
static/katex/fonts/KaTeX_AMS-Regular.woff2 → web/static/katex/fonts/KaTeX_AMS-Regular.woff2 renamed
static/katex/fonts/KaTeX_Caligraphic-Bold.woff2 → web/static/katex/fonts/KaTeX_Caligraphic-Bold.woff2 renamed
static/katex/fonts/KaTeX_Caligraphic-Regular.woff2 → web/static/katex/fonts/KaTeX_Caligraphic-Regular.woff2 renamed
static/katex/fonts/KaTeX_Fraktur-Bold.woff2 → web/static/katex/fonts/KaTeX_Fraktur-Bold.woff2 renamed
static/katex/fonts/KaTeX_Fraktur-Regular.woff2 → web/static/katex/fonts/KaTeX_Fraktur-Regular.woff2 renamed
static/katex/fonts/KaTeX_Main-Bold.woff2 → web/static/katex/fonts/KaTeX_Main-Bold.woff2 renamed
static/katex/fonts/KaTeX_Main-BoldItalic.woff2 → web/static/katex/fonts/KaTeX_Main-BoldItalic.woff2 renamed
static/katex/fonts/KaTeX_Main-Italic.woff2 → web/static/katex/fonts/KaTeX_Main-Italic.woff2 renamed
static/katex/fonts/KaTeX_Main-Regular.woff2 → web/static/katex/fonts/KaTeX_Main-Regular.woff2 renamed
static/katex/fonts/KaTeX_Math-BoldItalic.woff2 → web/static/katex/fonts/KaTeX_Math-BoldItalic.woff2 renamed
static/katex/fonts/KaTeX_Math-Italic.woff2 → web/static/katex/fonts/KaTeX_Math-Italic.woff2 renamed
static/katex/fonts/KaTeX_SansSerif-Bold.woff2 → web/static/katex/fonts/KaTeX_SansSerif-Bold.woff2 renamed
static/katex/fonts/KaTeX_SansSerif-Italic.woff2 → web/static/katex/fonts/KaTeX_SansSerif-Italic.woff2 renamed
static/katex/fonts/KaTeX_SansSerif-Regular.woff2 → web/static/katex/fonts/KaTeX_SansSerif-Regular.woff2 renamed
static/katex/fonts/KaTeX_Script-Regular.woff2 → web/static/katex/fonts/KaTeX_Script-Regular.woff2 renamed
static/katex/fonts/KaTeX_Size1-Regular.woff2 → web/static/katex/fonts/KaTeX_Size1-Regular.woff2 renamed
static/katex/fonts/KaTeX_Size2-Regular.woff2 → web/static/katex/fonts/KaTeX_Size2-Regular.woff2 renamed
static/katex/fonts/KaTeX_Size3-Regular.woff2 → web/static/katex/fonts/KaTeX_Size3-Regular.woff2 renamed
static/katex/fonts/KaTeX_Size4-Regular.woff2 → web/static/katex/fonts/KaTeX_Size4-Regular.woff2 renamed
static/katex/fonts/KaTeX_Typewriter-Regular.woff2 → web/static/katex/fonts/KaTeX_Typewriter-Regular.woff2 renamed
static/katex/katex.min.css → web/static/katex/katex.min.css renamed
static/katex/katex.min.js → web/static/katex/katex.min.js renamed
static/logo.png → web/static/logo.png renamed
static/math.js → web/static/math.js renamed
static/style.css → web/static/style.css renamed
| @@ -394,10 +394,11 @@ pre.rangediff,pre.body{white-space:pre-wrap;overflow-wrap:anywhere} | ||
| 394 | 394 | .hit.org .hitavatar img{border-radius:6px} |
| 395 | 395 | .hitavatar img{display:flex;width:3rem;height:3rem;border-radius:0;object-fit:cover;border:1px solid var(--line)} |
| 396 | 396 | .hit .bio{color:var(--fg2);overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical} |
| 397 | -.langbar{display:flex;height:.5rem;border-radius:4px;overflow:hidden;margin:.2rem 0 .5rem;background:var(--line)} | |
| 397 | +.langbar{display:flex;gap:2px;height:.5rem;border-radius:4px;overflow:hidden;margin:.2rem 0 .6rem} | |
| 398 | 398 | .langbar span{display:block;height:100%} |
| 399 | 399 | .langlist{list-style:none;margin:0 0 1.2rem;padding:0;font-size:.85rem;display:flex;flex-wrap:wrap;gap:.2rem 1rem} |
| 400 | -.langlist .pct{color:var(--muted);margin-left:.25rem} | |
| 400 | +.langlist b{font-weight:600} | |
| 401 | +.langlist .pct{color:var(--muted);margin-left:.3rem} | |
| 401 | 402 | @media(max-width:800px){.bigsearch{margin-left:0}.searchpage{grid-template-columns:1fr;gap:.6rem}.navsearch{order:5;flex-basis:100%;margin:0}.facets{display:flex;flex-direction:column;gap:.5rem}.facetbox{padding:.4rem}.facetbox h4{display:none}.facetlist{flex-direction:row;flex-wrap:wrap;gap:.35rem}.facetlist a{border:1px solid var(--line);padding:.2rem .55rem}.facetlist a.active{border-color:var(--accent);box-shadow:none}} |
| 402 | 403 | .facetbox{margin-bottom:1.4rem} |
| 403 | 404 | .facetbox h4{font-size:.75rem;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin:.15rem .4rem .45rem;font-weight:600} |
templates/admin.html → web/templates/admin.tmpl renamed
templates/appearance.html → web/templates/appearance.tmpl renamed
templates/blame.html → web/templates/blame.tmpl renamed
templates/blob.html → web/templates/blob.tmpl renamed
templates/commit.html → web/templates/commit.tmpl renamed
templates/compare.html → web/templates/compare.tmpl renamed
templates/create.html → web/templates/create.tmpl renamed
templates/edit.html → web/templates/edit.tmpl renamed
templates/error.html → web/templates/error.tmpl renamed
templates/index.html → web/templates/index.tmpl renamed
templates/interdiff.html → web/templates/interdiff.tmpl renamed
templates/issue.html → web/templates/issue.tmpl renamed
templates/issues.html → web/templates/issues.tmpl renamed
templates/keys.html → web/templates/keys.tmpl renamed
templates/layout.html → web/templates/layout.tmpl renamed
templates/log.html → web/templates/log.tmpl renamed
templates/login.html → web/templates/login.tmpl renamed
templates/message.html → web/templates/message.tmpl renamed
templates/meta.html → web/templates/meta.tmpl renamed
templates/newissue.html → web/templates/newissue.tmpl renamed
templates/neworg.html → web/templates/neworg.tmpl renamed
templates/newpull.html → web/templates/newpull.tmpl renamed
templates/notifications.html → web/templates/notifications.tmpl renamed
templates/org.html → web/templates/org.tmpl renamed
templates/orgsettings.html → web/templates/orgsettings.tmpl renamed
templates/pull.html → web/templates/pull.tmpl renamed
templates/pulls.html → web/templates/pulls.tmpl renamed
templates/recovery.html → web/templates/recovery.tmpl renamed
templates/refs.html → web/templates/refs.tmpl renamed
templates/register.html → web/templates/register.tmpl renamed
templates/repo.html → web/templates/repo.tmpl renamed
| @@ -18,7 +18,7 @@ git push -u origin {{$p.Ref}}</pre> | ||
| 18 | 18 | <div class="clone"> |
| 19 | 19 | {{with .Data.Languages}}<h3>languages</h3> |
| 20 | 20 | <div class="langbar">{{range .}}<span style="width:{{printf "%.2f" .Percent}}%;background:{{.Colour}}" title="{{.Name}} {{printf "%.1f" .Percent}}%"></span>{{end}}</div> |
| 21 | -<ul class="langlist">{{range .}}<li><i class="dot" style="background:{{.Colour}}"></i>{{.Name}}<span class="pct">{{printf "%.1f" .Percent}}%</span></li>{{end}}</ul> | |
| 21 | +<ul class="langlist">{{range .}}<li><i class="dot" style="background:{{.Colour}}"></i><b>{{.Name}}</b><span class="pct">{{printf "%.1f" .Percent}}%</span></li>{{end}}</ul> | |
| 22 | 22 | <h3 class="rule">clone</h3>{{else}}<h3>clone</h3>{{end}} |
| 23 | 23 | <strong>read only</strong> |
| 24 | 24 | <div><a href="https://{{.Host}}/{{$p.Repo.Path}}">https://{{.Host}}/{{$p.Repo.Path}}</a></div> |
templates/repos.html → web/templates/repos.tmpl renamed
templates/reposettings.html → web/templates/reposettings.tmpl renamed
templates/reset.html → web/templates/reset.tmpl renamed
templates/resetpassword.html → web/templates/resetpassword.tmpl renamed
templates/search.html → web/templates/search.tmpl renamed
templates/security.html → web/templates/security.tmpl renamed
templates/settings.html → web/templates/settings.tmpl renamed
templates/stars.html → web/templates/stars.tmpl renamed
templates/tokens.html → web/templates/tokens.tmpl renamed
templates/totp.html → web/templates/totp.tmpl renamed
templates/totpsetup.html → web/templates/totpsetup.tmpl renamed
templates/tree.html → web/templates/tree.tmpl renamed
templates/user.html → web/templates/user.tmpl renamed
web/web.go added
| @@ -0,0 +1,9 @@ | ||
| 1 | +package web | |
| 2 | + | |
| 3 | +import "embed" | |
| 4 | + | |
| 5 | +//go:embed templates/*.tmpl | |
| 6 | +var Templates embed.FS | |
| 7 | + | |
| 8 | +//go:embed static | |
| 9 | +var Static embed.FS | |