Detailed changes
@@ -4,7 +4,7 @@ import (
"log"
"strings"
- gm "github.com/charmbracelet/soft-serve/server/git"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/gliderlabs/ssh"
gossh "golang.org/x/crypto/ssh"
)
@@ -39,40 +39,40 @@ func (cfg *Config) Fetch(repo string, pk ssh.PublicKey) {
}
// AuthRepo grants repo authorization to the given key.
-func (cfg *Config) AuthRepo(repo string, pk ssh.PublicKey) gm.AccessLevel {
+func (cfg *Config) AuthRepo(repo string, pk ssh.PublicKey) proto.AccessLevel {
return cfg.accessForKey(repo, pk)
}
// PasswordHandler returns whether or not password access is allowed.
func (cfg *Config) PasswordHandler(ctx ssh.Context, password string) bool {
- return (cfg.AnonAccess != gm.NoAccess.String()) && cfg.AllowKeyless
+ return (cfg.AnonAccess != proto.NoAccess.String()) && cfg.AllowKeyless
}
// KeyboardInteractiveHandler returns whether or not keyboard interactive is allowed.
func (cfg *Config) KeyboardInteractiveHandler(ctx ssh.Context, _ gossh.KeyboardInteractiveChallenge) bool {
- return (cfg.AnonAccess != gm.NoAccess.String()) && cfg.AllowKeyless
+ return (cfg.AnonAccess != proto.NoAccess.String()) && cfg.AllowKeyless
}
// PublicKeyHandler returns whether or not the given public key may access the
// repo.
func (cfg *Config) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
- return cfg.accessForKey("", pk) != gm.NoAccess
+ return cfg.accessForKey("", pk) != proto.NoAccess
}
-func (cfg *Config) anonAccessLevel() gm.AccessLevel {
+func (cfg *Config) anonAccessLevel() proto.AccessLevel {
cfg.mtx.RLock()
defer cfg.mtx.RUnlock()
switch cfg.AnonAccess {
case "no-access":
- return gm.NoAccess
+ return proto.NoAccess
case "read-only":
- return gm.ReadOnlyAccess
+ return proto.ReadOnlyAccess
case "read-write":
- return gm.ReadWriteAccess
+ return proto.ReadWriteAccess
case "admin-access":
- return gm.AdminAccess
+ return proto.AdminAccess
default:
- return gm.NoAccess
+ return proto.NoAccess
}
}
@@ -82,7 +82,7 @@ func (cfg *Config) anonAccessLevel() gm.AccessLevel {
// config.AnonAccess.
// If repo exists, and private, then admins and collabs are allowed access.
// If repo exists, and not private, then access is based on config.AnonAccess.
-func (cfg *Config) accessForKey(repo string, pk ssh.PublicKey) gm.AccessLevel {
+func (cfg *Config) accessForKey(repo string, pk ssh.PublicKey) proto.AccessLevel {
anon := cfg.anonAccessLevel()
private := cfg.isPrivate(repo)
// Find user
@@ -92,24 +92,24 @@ func (cfg *Config) accessForKey(repo string, pk ssh.PublicKey) gm.AccessLevel {
apk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(strings.TrimSpace(k)))
if err != nil {
log.Printf("error: malformed authorized key: '%s'", k)
- return gm.NoAccess
+ return proto.NoAccess
}
if ssh.KeysEqual(pk, apk) {
if user.Admin {
- return gm.AdminAccess
+ return proto.AdminAccess
}
u := user
if cfg.isCollab(repo, &u) {
- if anon > gm.ReadWriteAccess {
+ if anon > proto.ReadWriteAccess {
return anon
}
- return gm.ReadWriteAccess
+ return proto.ReadWriteAccess
}
if !private {
- if anon > gm.ReadOnlyAccess {
+ if anon > proto.ReadOnlyAccess {
return anon
}
- return gm.ReadOnlyAccess
+ return proto.ReadOnlyAccess
}
}
}
@@ -118,7 +118,7 @@ func (cfg *Config) accessForKey(repo string, pk ssh.PublicKey) gm.AccessLevel {
// Don't restrict access to private repos if no users are configured.
// Return anon access level.
if private && len(cfg.Users) > 0 {
- return gm.NoAccess
+ return proto.NoAccess
}
return anon
}
@@ -3,7 +3,7 @@ package config
import (
"testing"
- "github.com/charmbracelet/soft-serve/server/git"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/gliderlabs/ssh"
"github.com/matryer/is"
)
@@ -18,12 +18,12 @@ func TestAuth(t *testing.T) {
cfg Config
repo string
key ssh.PublicKey
- access git.AccessLevel
+ access proto.AccessLevel
}{
// Repo access
{
name: "anon access: no-access, anonymous user",
- access: git.NoAccess,
+ access: proto.NoAccess,
repo: "foo",
cfg: Config{
AnonAccess: "no-access",
@@ -36,7 +36,7 @@ func TestAuth(t *testing.T) {
},
{
name: "anon access: no-access, anonymous user with admin user",
- access: git.NoAccess,
+ access: proto.NoAccess,
repo: "foo",
cfg: Config{
AnonAccess: "no-access",
@@ -59,7 +59,7 @@ func TestAuth(t *testing.T) {
name: "anon access: no-access, authd user",
key: dummyPk,
repo: "foo",
- access: git.ReadOnlyAccess,
+ access: proto.ReadOnlyAccess,
cfg: Config{
AnonAccess: "no-access",
Repos: []RepoConfig{
@@ -80,7 +80,7 @@ func TestAuth(t *testing.T) {
name: "anon access: no-access, anonymous user with admin user",
key: dummyPk,
repo: "foo",
- access: git.NoAccess,
+ access: proto.NoAccess,
cfg: Config{
AnonAccess: "no-access",
Repos: []RepoConfig{
@@ -102,7 +102,7 @@ func TestAuth(t *testing.T) {
name: "anon access: no-access, admin user",
repo: "foo",
key: adminPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "no-access",
Repos: []RepoConfig{
@@ -123,7 +123,7 @@ func TestAuth(t *testing.T) {
{
name: "anon access: read-only, anonymous user",
repo: "foo",
- access: git.ReadOnlyAccess,
+ access: proto.ReadOnlyAccess,
cfg: Config{
AnonAccess: "read-only",
Repos: []RepoConfig{
@@ -137,7 +137,7 @@ func TestAuth(t *testing.T) {
name: "anon access: read-only, authd user",
repo: "foo",
key: dummyPk,
- access: git.ReadOnlyAccess,
+ access: proto.ReadOnlyAccess,
cfg: Config{
AnonAccess: "read-only",
Repos: []RepoConfig{
@@ -158,7 +158,7 @@ func TestAuth(t *testing.T) {
name: "anon access: read-only, admin user",
repo: "foo",
key: adminPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "read-only",
Repos: []RepoConfig{
@@ -179,7 +179,7 @@ func TestAuth(t *testing.T) {
{
name: "anon access: read-write, anonymous user",
repo: "foo",
- access: git.ReadWriteAccess,
+ access: proto.ReadWriteAccess,
cfg: Config{
AnonAccess: "read-write",
Repos: []RepoConfig{
@@ -193,7 +193,7 @@ func TestAuth(t *testing.T) {
name: "anon access: read-write, authd user",
repo: "foo",
key: dummyPk,
- access: git.ReadWriteAccess,
+ access: proto.ReadWriteAccess,
cfg: Config{
AnonAccess: "read-write",
Repos: []RepoConfig{
@@ -213,7 +213,7 @@ func TestAuth(t *testing.T) {
name: "anon access: read-write, admin user",
repo: "foo",
key: adminPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "read-write",
Repos: []RepoConfig{
@@ -234,7 +234,7 @@ func TestAuth(t *testing.T) {
{
name: "anon access: admin-access, anonymous user",
repo: "foo",
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "admin-access",
Repos: []RepoConfig{
@@ -248,7 +248,7 @@ func TestAuth(t *testing.T) {
name: "anon access: admin-access, authd user",
repo: "foo",
key: dummyPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "admin-access",
Repos: []RepoConfig{
@@ -268,7 +268,7 @@ func TestAuth(t *testing.T) {
name: "anon access: admin-access, admin user",
repo: "foo",
key: adminPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "admin-access",
Repos: []RepoConfig{
@@ -292,7 +292,7 @@ func TestAuth(t *testing.T) {
name: "anon access: no-access, authd user, collab",
key: dummyPk,
repo: "foo",
- access: git.ReadWriteAccess,
+ access: proto.ReadWriteAccess,
cfg: Config{
AnonAccess: "no-access",
Repos: []RepoConfig{
@@ -317,7 +317,7 @@ func TestAuth(t *testing.T) {
name: "anon access: no-access, authd user, collab, private repo",
key: dummyPk,
repo: "foo",
- access: git.ReadWriteAccess,
+ access: proto.ReadWriteAccess,
cfg: Config{
AnonAccess: "no-access",
Repos: []RepoConfig{
@@ -343,7 +343,7 @@ func TestAuth(t *testing.T) {
name: "anon access: no-access, admin user, collab, private repo",
repo: "foo",
key: adminPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "no-access",
Repos: []RepoConfig{
@@ -370,7 +370,7 @@ func TestAuth(t *testing.T) {
name: "anon access: read-only, authd user, collab, private repo",
repo: "foo",
key: dummyPk,
- access: git.ReadWriteAccess,
+ access: proto.ReadWriteAccess,
cfg: Config{
AnonAccess: "read-only",
Repos: []RepoConfig{
@@ -395,7 +395,7 @@ func TestAuth(t *testing.T) {
{
name: "anon access: admin-access, anonymous user, collab",
repo: "foo",
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "admin-access",
Repos: []RepoConfig{
@@ -412,7 +412,7 @@ func TestAuth(t *testing.T) {
name: "anon access: admin-access, authd user, collab",
repo: "foo",
key: dummyPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "admin-access",
Repos: []RepoConfig{
@@ -436,7 +436,7 @@ func TestAuth(t *testing.T) {
name: "anon access: admin-access, admin user, collab",
repo: "foo",
key: adminPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "admin-access",
Repos: []RepoConfig{
@@ -462,7 +462,7 @@ func TestAuth(t *testing.T) {
// New repo
{
name: "anon access: no-access, anonymous user, new repo",
- access: git.NoAccess,
+ access: proto.NoAccess,
repo: "foo",
cfg: Config{
AnonAccess: "no-access",
@@ -472,7 +472,7 @@ func TestAuth(t *testing.T) {
name: "anon access: no-access, authd user, new repo",
key: dummyPk,
repo: "foo",
- access: git.ReadOnlyAccess,
+ access: proto.ReadOnlyAccess,
cfg: Config{
AnonAccess: "no-access",
Users: []User{
@@ -488,7 +488,7 @@ func TestAuth(t *testing.T) {
name: "anon access: no-access, authd user, new repo, with user",
key: dummyPk,
repo: "foo",
- access: git.NoAccess,
+ access: proto.NoAccess,
cfg: Config{
AnonAccess: "no-access",
Users: []User{
@@ -504,7 +504,7 @@ func TestAuth(t *testing.T) {
name: "anon access: no-access, admin user, new repo",
repo: "foo",
key: adminPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "no-access",
Users: []User{
@@ -520,7 +520,7 @@ func TestAuth(t *testing.T) {
{
name: "anon access: read-only, anonymous user, new repo",
repo: "foo",
- access: git.ReadOnlyAccess,
+ access: proto.ReadOnlyAccess,
cfg: Config{
AnonAccess: "read-only",
},
@@ -529,7 +529,7 @@ func TestAuth(t *testing.T) {
name: "anon access: read-only, authd user, new repo",
repo: "foo",
key: dummyPk,
- access: git.ReadOnlyAccess,
+ access: proto.ReadOnlyAccess,
cfg: Config{
AnonAccess: "read-only",
Users: []User{
@@ -545,7 +545,7 @@ func TestAuth(t *testing.T) {
name: "anon access: read-only, admin user, new repo",
repo: "foo",
key: adminPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "read-only",
Users: []User{
@@ -561,7 +561,7 @@ func TestAuth(t *testing.T) {
{
name: "anon access: read-write, anonymous user, new repo",
repo: "foo",
- access: git.ReadWriteAccess,
+ access: proto.ReadWriteAccess,
cfg: Config{
AnonAccess: "read-write",
},
@@ -570,7 +570,7 @@ func TestAuth(t *testing.T) {
name: "anon access: read-write, authd user, new repo",
repo: "foo",
key: dummyPk,
- access: git.ReadWriteAccess,
+ access: proto.ReadWriteAccess,
cfg: Config{
AnonAccess: "read-write",
Users: []User{
@@ -586,7 +586,7 @@ func TestAuth(t *testing.T) {
name: "anon access: read-write, admin user, new repo",
repo: "foo",
key: adminPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "read-write",
Users: []User{
@@ -602,7 +602,7 @@ func TestAuth(t *testing.T) {
{
name: "anon access: admin-access, anonymous user, new repo",
repo: "foo",
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "admin-access",
},
@@ -611,7 +611,7 @@ func TestAuth(t *testing.T) {
name: "anon access: admin-access, authd user, new repo",
repo: "foo",
key: dummyPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "admin-access",
Users: []User{
@@ -627,7 +627,7 @@ func TestAuth(t *testing.T) {
name: "anon access: admin-access, admin user, new repo",
repo: "foo",
key: adminPk,
- access: git.AdminAccess,
+ access: proto.AdminAccess,
cfg: Config{
AnonAccess: "admin-access",
Users: []User{
@@ -645,7 +645,7 @@ func TestAuth(t *testing.T) {
{
name: "anon access: read-only, no users",
repo: "foo",
- access: git.ReadOnlyAccess,
+ access: proto.ReadOnlyAccess,
cfg: Config{
AnonAccess: "read-only",
},
@@ -653,7 +653,7 @@ func TestAuth(t *testing.T) {
{
name: "anon access: read-write, no users",
repo: "foo",
- access: git.ReadWriteAccess,
+ access: proto.ReadWriteAccess,
cfg: Config{
AnonAccess: "read-write",
},
@@ -19,8 +19,8 @@ import (
"os"
"github.com/charmbracelet/soft-serve/git"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/charmbracelet/soft-serve/server/config"
- gm "github.com/charmbracelet/soft-serve/server/git"
"github.com/go-git/go-billy/v5/memfs"
ggit "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
@@ -103,9 +103,9 @@ func NewConfig(cfg *config.Config) (*Config, error) {
c.Source = rs
// Grant read-write access when no keys are provided.
if len(pks) == 0 {
- anonAccess = gm.ReadWriteAccess.String()
+ anonAccess = proto.ReadWriteAccess.String()
} else {
- anonAccess = gm.ReadOnlyAccess.String()
+ anonAccess = proto.ReadOnlyAccess.String()
}
if host == "" {
displayHost = "localhost"
@@ -0,0 +1,44 @@
+package git
+
+// ConfigOptions are options for Config.
+type ConfigOptions struct {
+ File string
+ All bool
+ Add bool
+}
+
+// Config gets a git configuration.
+func Config(key string, opts ...ConfigOptions) (string, error) {
+ var opt ConfigOptions
+ if len(opts) > 0 {
+ opt = opts[0]
+ }
+ cmd := NewCommand("config")
+ if opt.File != "" {
+ cmd.AddArgs("--file", opt.File)
+ }
+ if opt.All {
+ cmd.AddArgs("--get-all")
+ }
+ cmd.AddArgs(key)
+ bts, err := cmd.Run()
+ if err != nil {
+ return "", err
+ }
+ return string(bts), nil
+}
+
+// SetConfig sets a git configuration.
+func SetConfig(key string, value string, opts ...ConfigOptions) error {
+ var opt ConfigOptions
+ if len(opts) > 0 {
+ opt = opts[0]
+ }
+ cmd := NewCommand("config")
+ if opt.File != "" {
+ cmd.AddArgs("--file", opt.File)
+ }
+ cmd.AddArgs(key, value)
+ _, err := cmd.Run()
+ return err
+}
@@ -207,3 +207,31 @@ func (r *Repository) UpdateServerInfo() error {
_, err := cmd.RunInDir(r.Path)
return err
}
+
+// Config returns the config value for the given key.
+func (r *Repository) Config(key string, opts ...ConfigOptions) (string, error) {
+ dir, err := gitDir(r.Repository)
+ if err != nil {
+ return "", err
+ }
+ var opt ConfigOptions
+ if len(opts) > 0 {
+ opt = opts[0]
+ }
+ opt.File = filepath.Join(dir, "config")
+ return Config(key, opt)
+}
+
+// SetConfig sets the config value for the given key.
+func (r *Repository) SetConfig(key, value string, opts ...ConfigOptions) error {
+ dir, err := gitDir(r.Repository)
+ if err != nil {
+ return err
+ }
+ var opt ConfigOptions
+ if len(opts) > 0 {
+ opt = opts[0]
+ }
+ opt.File = filepath.Join(dir, "config")
+ return SetConfig(key, value, opt)
+}
@@ -33,6 +33,7 @@ require (
github.com/spf13/cobra v1.6.1
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gopkg.in/yaml.v3 v3.0.1
+ modernc.org/sqlite v1.19.5
)
require (
@@ -47,10 +48,12 @@ require (
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
+ github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
+ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
@@ -64,18 +67,31 @@ require (
github.com/muesli/mango v0.1.0 // indirect
github.com/muesli/mango-pflag v0.1.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sahilm/fuzzy v0.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/yuin/goldmark v1.5.2 // indirect
github.com/yuin/goldmark-emoji v1.0.1 // indirect
+ golang.org/x/mod v0.3.0 // indirect
golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/text v0.3.7 // indirect
+ golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect
+ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
+ lukechampine.com/uint128 v1.2.0 // indirect
+ modernc.org/cc/v3 v3.40.0 // indirect
+ modernc.org/ccgo/v3 v3.16.13 // indirect
+ modernc.org/libc v1.21.5 // indirect
+ modernc.org/mathutil v1.5.0 // indirect
+ modernc.org/memory v1.4.0 // indirect
+ modernc.org/opt v0.1.3 // indirect
+ modernc.org/strutil v1.1.3 // indirect
+ modernc.org/token v1.0.1 // indirect
)
// see https://github.com/sergi/go-diff/issues/123
@@ -43,6 +43,9 @@ github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87ini
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
github.com/charmbracelet/wish v0.7.0 h1:rdfacCWaKCQpCMPbOKfi68GYqsb+9CnUzN1Ov/INZJ0=
github.com/charmbracelet/wish v0.7.0/go.mod h1:16EQz7k3hEgPkPENghcpEddvlrmucIudE0jnczKr+k4=
+github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
+github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
+github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -76,11 +79,18 @@ github.com/gogs/git-module v1.7.1/go.mod h1:Y3rsSqtFZEbn7lp+3gWf42GKIY1eNTtLt7Jr
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
+github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
+github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@@ -89,6 +99,8 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o=
github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
@@ -120,6 +132,8 @@ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
+github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk=
github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
@@ -156,6 +170,8 @@ 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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -191,26 +207,39 @@ github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5ta
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20200930185726-fdedc70b468f/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=
@@ -222,6 +251,7 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
@@ -230,13 +260,21 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
+golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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=
@@ -252,3 +290,44 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
+lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
+lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
+modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
+modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
+modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
+modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI=
+modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0=
+modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g=
+modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
+modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
+modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
+modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
+modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
+modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
+modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA=
+modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0=
+modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
+modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
+modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
+modernc.org/libc v1.21.5 h1:xBkU9fnHV+hvZuPSRszN0AXDG4M7nwPLwTWwkYcvLCI=
+modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
+modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
+modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
+modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
+modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
+modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
+modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/sqlite v1.19.5 h1:E3iHL55c1Vw1knqIeU9N7B0fSjuiOjHZo7iVMsO6U5U=
+modernc.org/sqlite v1.19.5/go.mod h1:EsYz8rfOvLCiYTy5ZFsOYzoCcRMu98YYkwAcCw5YIYw=
+modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
+modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
+modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
+modernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE=
+modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
+modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
+modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
@@ -0,0 +1,34 @@
+package proto
+
+// AccessLevel is the level of access allowed to a repo.
+type AccessLevel int
+
+const (
+ // NoAccess does not allow access to the repo.
+ NoAccess AccessLevel = iota
+
+ // ReadOnlyAccess allows read-only access to the repo.
+ ReadOnlyAccess
+
+ // ReadWriteAccess allows read and write access to the repo.
+ ReadWriteAccess
+
+ // AdminAccess allows read, write, and admin access to the repo.
+ AdminAccess
+)
+
+// String implements the Stringer interface for AccessLevel.
+func (a AccessLevel) String() string {
+ switch a {
+ case NoAccess:
+ return "no-access"
+ case ReadOnlyAccess:
+ return "read-only"
+ case ReadWriteAccess:
+ return "read-write"
+ case AdminAccess:
+ return "admin-access"
+ default:
+ return ""
+ }
+}
@@ -0,0 +1,7 @@
+package proto
+
+// Provider is a repository provider.
+type Provider interface {
+ // Open opens a repository.
+ Open(name string) (RepositoryService, error)
+}
@@ -0,0 +1,17 @@
+package proto
+
+// Repository is Git repository.
+type Repository interface {
+ Name() string
+ ProjectName() string
+ Description() string
+ IsPrivate() bool
+}
+
+// RepositoryService is a service for managing repositories metadata.
+type RepositoryService interface {
+ Repository
+ SetProjectName(string) error
+ SetDescription(string) error
+ SetPrivate(bool) error
+}
@@ -8,7 +8,7 @@ import (
gansi "github.com/charmbracelet/glamour/ansi"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/soft-serve/config"
- gm "github.com/charmbracelet/soft-serve/server/git"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/charmbracelet/soft-serve/ui/common"
"github.com/muesli/termenv"
"github.com/spf13/cobra"
@@ -37,7 +37,7 @@ func CatCommand() *cobra.Command {
rn := ps[0]
fp := strings.Join(ps[1:], "/")
auth := ac.AuthRepo(rn, s.PublicKey())
- if auth < gm.ReadOnlyAccess {
+ if auth < proto.ReadOnlyAccess {
return ErrUnauthorized
}
var repo *config.Repo
@@ -5,7 +5,7 @@ import (
"os/exec"
"github.com/charmbracelet/soft-serve/config"
- gm "github.com/charmbracelet/soft-serve/server/git"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/spf13/cobra"
)
@@ -17,7 +17,7 @@ func GitCommand() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
ac, s := fromContext(cmd)
auth := ac.AuthRepo("config", s.PublicKey())
- if auth < gm.AdminAccess {
+ if auth < proto.AdminAccess {
return ErrUnauthorized
}
if len(args) < 1 {
@@ -6,7 +6,7 @@ import (
"strings"
"github.com/charmbracelet/soft-serve/git"
- gm "github.com/charmbracelet/soft-serve/server/git"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/spf13/cobra"
)
@@ -27,13 +27,13 @@ func ListCommand() *cobra.Command {
ps = strings.Split(path, "/")
rn = ps[0]
auth := ac.AuthRepo(rn, s.PublicKey())
- if auth < gm.ReadOnlyAccess {
+ if auth < proto.ReadOnlyAccess {
return ErrUnauthorized
}
}
if path == "" || path == "." || path == "/" {
for _, r := range ac.Source.AllRepos() {
- if ac.AuthRepo(r.Repo(), s.PublicKey()) >= gm.ReadOnlyAccess {
+ if ac.AuthRepo(r.Repo(), s.PublicKey()) >= proto.ReadOnlyAccess {
fmt.Fprintln(s, r.Repo())
}
}
@@ -1,7 +1,7 @@
package cmd
import (
- gm "github.com/charmbracelet/soft-serve/server/git"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/spf13/cobra"
)
@@ -13,7 +13,7 @@ func ReloadCommand() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
ac, s := fromContext(cmd)
auth := ac.AuthRepo("config", s.PublicKey())
- if auth < gm.AdminAccess {
+ if auth < proto.AdminAccess {
return ErrUnauthorized
}
return ac.Reload()
@@ -0,0 +1,74 @@
+package db
+
+import (
+ "github.com/charmbracelet/soft-serve/server/db/types"
+)
+
+// ConfigStore is a configuration database storage.
+type ConfigStore interface {
+ // Config
+ GetConfig() (*types.Config, error)
+ SetConfigName(string) error
+ SetConfigHost(string) error
+ SetConfigPort(int) error
+ SetConfigAnonAccess(string) error
+ SetConfigAllowKeyless(bool) error
+}
+
+// UserStore is a user database storage.
+type UserStore interface {
+ // Users
+ AddUser(name, login, email, password string, isAdmin bool) error
+ DeleteUser(int) error
+ GetUser(int) (*types.User, error)
+ GetUserByLogin(string) (*types.User, error)
+ GetUserByEmail(string) (*types.User, error)
+ GetUserByPublicKey(string) (*types.User, error)
+ SetUserName(*types.User, string) error
+ SetUserLogin(*types.User, string) error
+ SetUserEmail(*types.User, string) error
+ SetUserPassword(*types.User, string) error
+ SetUserAdmin(*types.User, bool) error
+}
+
+// PublicKeyStore is a public key database storage.
+type PublicKeyStore interface {
+ // Public keys
+ AddUserPublicKey(*types.User, string) error
+ DeleteUserPublicKey(int) error
+ GetUserPublicKeys(*types.User) ([]*types.PublicKey, error)
+}
+
+// RepoStore is a repository database storage.
+type RepoStore interface {
+ // Repos
+ AddRepo(name, projectName, description string, isPrivate bool) error
+ DeleteRepo(string) error
+ GetRepo(string) (*types.Repo, error)
+ SetRepoProjectName(string, string) error
+ SetRepoDescription(string, string) error
+ SetRepoPrivate(string, bool) error
+}
+
+// CollabStore is a collaborator database storage.
+type CollabStore interface {
+ // Collaborators
+ AddRepoCollab(*types.Repo, *types.User) error
+ DeleteRepoCollab(int, int) error
+ ListRepoCollabs(*types.Repo) ([]*types.User, error)
+}
+
+// DB is a database.
+type DB interface {
+ ConfigStore
+ UserStore
+ PublicKeyStore
+ RepoStore
+ CollabStore
+
+ // CreateDB creates the database.
+ CreateDB() error
+
+ // Close closes the database.
+ Close() error
+}
@@ -0,0 +1,58 @@
+package sqlite
+
+import (
+ "github.com/charmbracelet/soft-serve/proto"
+ "github.com/charmbracelet/soft-serve/server/db/types"
+)
+
+// Open opens a repository.
+func (d *Sqlite) Open(name string) (proto.RepositoryService, error) {
+ r, err := d.GetRepo(name)
+ if err != nil {
+ return nil, err
+ }
+ return &repository{
+ repo: r,
+ db: d,
+ }, nil
+}
+
+type repository struct {
+ repo *types.Repo
+ db *Sqlite
+}
+
+// Name returns the repository's name.
+func (r *repository) Name() string {
+ return r.repo.Name
+}
+
+// ProjectName returns the repository's project name.
+func (r *repository) ProjectName() string {
+ return r.repo.ProjectName
+}
+
+// SetProjectName sets the repository's project name.
+func (r *repository) SetProjectName(name string) error {
+ return r.db.SetRepoProjectName(r.repo.Name, name)
+}
+
+// Description returns the repository's description.
+func (r *repository) Description() string {
+ return r.repo.Description
+}
+
+// SetDescription sets the repository's description.
+func (r *repository) SetDescription(desc string) error {
+ return r.db.SetRepoDescription(r.repo.Name, desc)
+}
+
+// IsPrivate returns whether the repository is private.
+func (r *repository) IsPrivate() bool {
+ return r.repo.Private
+}
+
+// SetPrivate sets whether the repository is private.
+func (r *repository) SetPrivate(p bool) error {
+ return r.db.SetRepoPrivate(r.repo.Name, p)
+}
@@ -0,0 +1,107 @@
+package sqlite
+
+var (
+ sqlCreateConfigTable = `CREATE TABLE IF NOT EXISTS config (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ host TEXT NOT NULL,
+ port INTEGER NOT NULL,
+ anon_access TEXT NOT NULL,
+ allow_keyless BOOLEAN NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL
+ );
+ `
+
+ sqlCreateUserTable = `CREATE TABLE IF NOT EXISTS user (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ login TEXT UNIQUE,
+ email TEXT UNIQUE,
+ password TEXT,
+ admin BOOLEAN NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL
+ );`
+
+ sqlCreatePublicKeyTable = `CREATE TABLE IF NOT EXISTS public_key (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER NOT NULL,
+ public_key TEXT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL,
+ UNIQUE (user_id, public_key),
+ CONSTRAINT user_id_fk
+ FOREIGN KEY(user_id) REFERENCES user(id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE
+ );`
+
+ sqlCreateRepoTable = `CREATE TABLE IF NOT EXISTS repo (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL UNIQUE,
+ project_name TEXT NOT NULL,
+ description TEXT NOT NULL,
+ private BOOLEAN NOT NULL,
+ create_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL,
+ );`
+
+ sqlCreateCollabTable = `CREATE TABLE IF NOT EXISTS collab (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER NOT NULL,
+ repo_id INTEGER NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL,
+ UNIQUE (user_id, repo_id),
+ CONSTRAINT user_id_fk
+ FOREIGN KEY(user_id) REFERENCES user(id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
+ CONSTRAINT repo_id_fk
+ FOREIGN KEY(repo_id) REFERENCES repo(id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE
+ );`
+
+ // Config.
+ sqlInsertConfig = `INSERT INTO config (name, host, port, anon_access, allow_keyless, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP);`
+ sqlSelectConfig = `SELECT id, name, host, port, anon_access, allow_keyless, created_at, updated_at FROM config WHERE id = ?;`
+ sqlUpdateConfigName = `UPDATE config SET name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
+ sqlUpdateConfigHost = `UPDATE config SET host = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
+ sqlUpdateConfigPort = `UPDATE config SET port = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
+ sqlUpdateConfigAnon = `UPDATE config SET anon_access = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
+ sqlUpdateConfigKeyless = `UPDATE config SET allow_keyless = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
+
+ // User.
+ sqlInsertUser = `INSERT INTO user (name, login, email, password, admin, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP);`
+ sqlDeleteUser = `DELETE FROM user WHERE id = ?;`
+ sqlSelectUser = `SELECT id, name, login, email, password, admin, created_at, updated_at FROM user WHERE id = ?;`
+ sqlSelectUserByLogin = `SELECT id, name, login, email, password, admin, created_at, updated_at FROM user WHERE login = ?;`
+ sqlSelectUserByEmail = `SELECT id, name, login, email, password, admin, created_at, updated_at FROM user WHERE email = ?;`
+ sqlSelectUserByPublicKey = `SELECT u.id, u.name, u.login, u.email, u.password, u.admin, u.created_at, u.updated_at FROM user u INNER JOIN public_key pk ON u.id = pk.user_id WHERE pk.public_key = ?;`
+ sqlUpdateUserName = `UPDATE user SET name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
+ sqlUpdateUserLogin = `UPDATE user SET login = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
+ sqlUpdateUserEmail = `UPDATE user SET email = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
+ sqlUpdateUserPassword = `UPDATE user SET password = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
+ sqlUpdateUserAdmin = `UPDATE user SET admin = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
+
+ // Public Key.
+ sqlInsertPublicKey = `INSERT INTO public_key (user_id, public_key, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP);`
+ sqlDeletePublicKey = `DELETE FROM public_key WHERE id = ?;`
+ sqlSelectUserPublicKeys = `SELECT id, user_id, public_key, created_at, updated_at FROM public_key WHERE user_id = ?;`
+
+ // Repo.
+ sqlInsertRepo = `INSERT INTO repo (name, project_name, description, private, updated_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP);`
+ sqlDeleteRepo = `DELETE FROM repo WHERE id = ?;`
+ sqlDeleteRepoWithName = `DELETE FROM repo WHERE name = ?;`
+ sqlSelectRepoByName = `SELECT id, name, project_name, description, private, created_at, updated_at FROM repo WHERE name = ?;`
+ sqlUpdateRepoProjectNameByName = `UPDATE repo SET project_name = ?, updated_at = CURRENT_TIMESTAMP WHERE name = ?;`
+ sqlUpdateRepoDescriptionByName = `UPDATE repo SET description = ?, updated_at = CURRENT_TIMESTAMP WHERE name = ?;`
+ sqlUpdateRepoPrivateByName = `UPDATE repo SET private = ?, updated_at = CURRENT_TIMESTAMP WHERE name = ?;`
+
+ // Collab.
+ sqlInsertCollab = `INSERT INTO collab (user_id, repo_id, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP);`
+ sqlDeleteCollab = `DELETE FROM collab WHERE user_id = ? AND repo_id;`
+ sqlSelectRepoCollabs = `SELECT user.id, user.name, user.login, user.email, user.admin, user.created_at, user.updated_at FROM user INNER JOIN collab ON user.id = collab.user_id WHERE collab.repo_id = ?;`
+)
@@ -0,0 +1,452 @@
+package sqlite
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "log"
+ "strings"
+ "time"
+
+ "github.com/charmbracelet/soft-serve/server/db"
+ "github.com/charmbracelet/soft-serve/server/db/types"
+ "modernc.org/sqlite"
+ sqlitelib "modernc.org/sqlite/lib"
+)
+
+var _ db.DB = &Sqlite{}
+
+// Sqlite is a SQLite database.
+type Sqlite struct {
+ path string
+ db *sql.DB
+}
+
+// New creates a new DB in the given path.
+func New(path string) (*Sqlite, error) {
+ var err error
+ log.Printf("Opening SQLite db: %s\n", path)
+ db, err := sql.Open("sqlite", path+
+ "?_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)")
+ if err != nil {
+ return nil, err
+ }
+ d := &Sqlite{
+ db: db,
+ path: path,
+ }
+ if err = d.CreateDB(); err != nil {
+ return nil, err
+ }
+ return d, d.db.Ping()
+}
+
+// Close closes the database.
+func (d *Sqlite) Close() error {
+ return d.db.Close()
+}
+
+// CreateDB creates the database and tables.
+func (d *Sqlite) CreateDB() error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ if _, err := tx.Exec(sqlInsertConfig); err != nil {
+ return err
+ }
+ if _, err := tx.Exec(sqlCreateUserTable); err != nil {
+ return err
+ }
+ if _, err := tx.Exec(sqlCreatePublicKeyTable); err != nil {
+ return err
+ }
+ if _, err := tx.Exec(sqlCreateRepoTable); err != nil {
+ return err
+ }
+ if _, err := tx.Exec(sqlCreateCollabTable); err != nil {
+ return err
+ }
+ return nil
+ })
+}
+
+const defaultConfigID = 1
+
+// GetConfig returns the server config.
+func (d *Sqlite) GetConfig() (*types.Config, error) {
+ var c types.Config
+ if err := d.wrapTransaction(func(tx *sql.Tx) error {
+ r := tx.QueryRow(sqlSelectConfig, defaultConfigID)
+ if err := r.Scan(&c.ID, &c.Name, &c.Host, &c.Port, &c.AnonAccess, &c.AllowKeyless, &c.CreatedAt, &c.UpdatedAt); err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return &c, nil
+}
+
+// SetConfigName sets the server config name.
+func (d *Sqlite) SetConfigName(name string) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateConfigName, name, defaultConfigID)
+ return err
+ })
+}
+
+// SetConfigHost sets the server config host.
+func (d *Sqlite) SetConfigHost(host string) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateConfigHost, host, defaultConfigID)
+ return err
+ })
+}
+
+// SetConfigPort sets the server config port.
+func (d *Sqlite) SetConfigPort(port int) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateConfigPort, port, defaultConfigID)
+ return err
+ })
+}
+
+// SetConfigAnonAccess sets the server config anon access.
+func (d *Sqlite) SetConfigAnonAccess(access string) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateConfigAnon, access, defaultConfigID)
+ return err
+ })
+}
+
+// SetConfigAllowKeyless sets the server config allow keyless.
+func (d *Sqlite) SetConfigAllowKeyless(allow bool) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateConfigKeyless, allow, defaultConfigID)
+ return err
+ })
+}
+
+// AddUser adds a new user.
+func (d *Sqlite) AddUser(name, login, email, password string, isAdmin bool) error {
+ var l *string
+ var e *string
+ var p *string
+ if login != "" {
+ login = strings.ToLower(login)
+ l = &login
+ }
+ if email != "" {
+ email = strings.ToLower(email)
+ e = &email
+ }
+ if password != "" {
+ p = &password
+ }
+ if err := d.wrapTransaction(func(tx *sql.Tx) error {
+ if _, err := tx.Exec(sqlInsertUser, name, l, e, p, isAdmin); err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ return err
+ }
+ return nil
+}
+
+// DeleteUser deletes a user.
+func (d *Sqlite) DeleteUser(id int) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlDeleteUser, id)
+ return err
+ })
+}
+
+// GetUser returns a user by ID.
+func (d *Sqlite) GetUser(id int) (*types.User, error) {
+ var u types.User
+ if err := d.wrapTransaction(func(tx *sql.Tx) error {
+ r := tx.QueryRow(sqlSelectUser, id)
+ if err := r.Scan(&u.ID, &u.Name, &u.Login, &u.Email, &u.Password, &u.Admin, &u.CreatedAt, &u.UpdatedAt); err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return &u, nil
+}
+
+// GetUserByLogin returns a user by login.
+func (d *Sqlite) GetUserByLogin(login string) (*types.User, error) {
+ login = strings.ToLower(login)
+ var u types.User
+ if err := d.wrapTransaction(func(tx *sql.Tx) error {
+ r := tx.QueryRow(sqlSelectUserByLogin, login)
+ if err := r.Scan(&u.ID, &u.Name, &u.Login, &u.Email, &u.Password, &u.Admin, &u.CreatedAt, &u.UpdatedAt); err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return &u, nil
+}
+
+// GetUserByLogin returns a user by login.
+func (d *Sqlite) GetUserByEmail(email string) (*types.User, error) {
+ email = strings.ToLower(email)
+ var u types.User
+ if err := d.wrapTransaction(func(tx *sql.Tx) error {
+ r := tx.QueryRow(sqlSelectUserByEmail, email)
+ if err := r.Scan(&u.ID, &u.Name, &u.Login, &u.Email, &u.Password, &u.Admin, &u.CreatedAt, &u.UpdatedAt); err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return &u, nil
+}
+
+// GetUserByPublicKey returns a user by public key.
+func (d *Sqlite) GetUserByPublicKey(key string) (*types.User, error) {
+ var u types.User
+ if err := d.wrapTransaction(func(tx *sql.Tx) error {
+ r := tx.QueryRow(sqlSelectUserByPublicKey, key)
+ if err := r.Scan(&u.ID, &u.Name, &u.Login, &u.Email, &u.Password, &u.Admin, &u.CreatedAt, &u.UpdatedAt); err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return &u, nil
+}
+
+// SetUserName sets the user name.
+func (d *Sqlite) SetUserName(user *types.User, name string) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateUserName, name, user.ID)
+ return err
+ })
+}
+
+// SetUserLogin sets the user login.
+func (d *Sqlite) SetUserLogin(user *types.User, login string) error {
+ if login == "" {
+ return fmt.Errorf("login cannot be empty")
+ }
+ login = strings.ToLower(login)
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateUserLogin, login, user.ID)
+ return err
+ })
+}
+
+// SetUserEmail sets the user email.
+func (d *Sqlite) SetUserEmail(user *types.User, email string) error {
+ if email == "" {
+ return fmt.Errorf("email cannot be empty")
+ }
+ email = strings.ToLower(email)
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateUserEmail, email, user.ID)
+ return err
+ })
+}
+
+// SetUserPassword sets the user password.
+func (d *Sqlite) SetUserPassword(user *types.User, password string) error {
+ if password == "" {
+ return fmt.Errorf("password cannot be empty")
+ }
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateUserPassword, password, user.ID)
+ return err
+ })
+}
+
+// SetUserAdmin sets the user admin.
+func (d *Sqlite) SetUserAdmin(user *types.User, admin bool) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateUserAdmin, admin, user.ID)
+ return err
+ })
+}
+
+// AddUserPublicKey adds a new user public key.
+func (d *Sqlite) AddUserPublicKey(user *types.User, key string) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlInsertPublicKey, user.ID, key)
+ return err
+ })
+}
+
+// DeleteUserPublicKey deletes a user public key.
+func (d *Sqlite) DeleteUserPublicKey(id int) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlDeletePublicKey, id)
+ return err
+ })
+}
+
+// GetUserPublicKeys returns the user public keys.
+func (d *Sqlite) GetUserPublicKeys(user *types.User) ([]*types.PublicKey, error) {
+ keys := make([]*types.PublicKey, 0)
+ if err := d.wrapTransaction(func(tx *sql.Tx) error {
+ rows, err := tx.Query(sqlSelectUserPublicKeys, user.ID)
+ if err != nil {
+ return err
+ }
+ if err := rows.Err(); err != nil {
+ return err
+ }
+ defer rows.Close()
+ for rows.Next() {
+ var k types.PublicKey
+ if err := rows.Scan(&k.ID, &k.UserID, &k.PublicKey, &k.CreatedAt, &k.UpdatedAt); err != nil {
+ return err
+ }
+ keys = append(keys, &k)
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return keys, nil
+}
+
+// AddRepo adds a new repo.
+func (d *Sqlite) AddRepo(name, projectName, description string, isPrivate bool) error {
+ name = strings.ToLower(name)
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlInsertRepo, name, projectName, description, isPrivate)
+ return err
+ })
+}
+
+// DeleteRepo deletes a repo.
+func (d *Sqlite) DeleteRepo(name string) error {
+ name = strings.ToLower(name)
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlDeleteRepoWithName, name)
+ return err
+ })
+}
+
+// GetRepo returns a repo by name.
+func (d *Sqlite) GetRepo(name string) (*types.Repo, error) {
+ name = strings.ToLower(name)
+ var r types.Repo
+ if err := d.wrapTransaction(func(tx *sql.Tx) error {
+ rows := tx.QueryRow(sqlSelectRepoByName, name)
+ if err := rows.Scan(&r.ID, &r.Name, &r.ProjectName, &r.Description, &r.Private, &r.CreatedAt, &r.UpdatedAt); err != nil {
+ return err
+ }
+ if err := rows.Err(); err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return &r, nil
+}
+
+// SetRepoProjectName sets the repo project name.
+func (d *Sqlite) SetRepoProjectName(name string, projectName string) error {
+ name = strings.ToLower(name)
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateRepoProjectNameByName, projectName, name)
+ return err
+ })
+}
+
+// SetRepoDescription sets the repo description.
+func (d *Sqlite) SetRepoDescription(name string, description string) error {
+ name = strings.ToLower(name)
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateRepoDescriptionByName, description,
+ name)
+ return err
+ })
+}
+
+// SetRepoPrivate sets the repo private.
+func (d *Sqlite) SetRepoPrivate(name string, private bool) error {
+ name = strings.ToLower(name)
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlUpdateRepoPrivateByName, private, name)
+ return err
+ })
+}
+
+// AddRepoCollab adds a new repo collaborator.
+func (d *Sqlite) AddRepoCollab(repo *types.Repo, user *types.User) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlInsertCollab, repo.ID, user.ID)
+ return err
+ })
+}
+
+// DeleteRepoCollab deletes a repo collaborator.
+func (d *Sqlite) DeleteRepoCollab(userID int, repoID int) error {
+ return d.wrapTransaction(func(tx *sql.Tx) error {
+ _, err := tx.Exec(sqlDeleteCollab, repoID, userID)
+ return err
+ })
+}
+
+// ListRepoCollabs returns a list of repo collaborators.
+func (d *Sqlite) ListRepoCollabs(repo *types.Repo) ([]*types.User, error) {
+ collabs := make([]*types.User, 0)
+ if err := d.wrapTransaction(func(tx *sql.Tx) error {
+ rows, err := tx.Query(sqlSelectRepoCollabs, repo.ID)
+ if err != nil {
+ return err
+ }
+ if err := rows.Err(); err != nil {
+ return err
+ }
+ defer rows.Close()
+ for rows.Next() {
+ var c types.User
+ if err := rows.Scan(&c.ID, &c.Name, &c.Login, &c.Email, &c.Admin, &c.CreatedAt, &c.UpdatedAt); err != nil {
+ return err
+ }
+ collabs = append(collabs, &c)
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return collabs, nil
+}
+
+// WrapTransaction runs the given function within a transaction.
+func (d *Sqlite) wrapTransaction(f func(tx *sql.Tx) error) error {
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
+ defer cancel()
+ tx, err := d.db.BeginTx(ctx, nil)
+ if err != nil {
+ log.Printf("error starting transaction: %s", err)
+ return err
+ }
+ for {
+ err = f(tx)
+ if err != nil {
+ serr, ok := err.(*sqlite.Error)
+ if ok && serr.Code() == sqlitelib.SQLITE_BUSY {
+ continue
+ }
+ log.Printf("error in transaction: %s", err)
+ return err
+ }
+ err = tx.Commit()
+ if err != nil {
+ log.Printf("error committing transaction: %s", err)
+ return err
+ }
+ break
+ }
+ return nil
+}
@@ -0,0 +1,15 @@
+package types
+
+import "time"
+
+// Config is the Soft Serve application configuration.
+type Config struct {
+ ID int
+ Name string
+ Host string
+ Port int
+ AnonAccess string
+ AllowKeyless bool
+ CreatedAt *time.Time
+ UpdatedAt *time.Time
+}
@@ -0,0 +1,64 @@
+package types
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "golang.org/x/crypto/ssh"
+)
+
+var _ ssh.PublicKey = &PublicKey{}
+var _ fmt.Stringer = &PublicKey{}
+
+// PublicKey is a public key database model.
+type PublicKey struct {
+ ID int
+ UserID int
+ PublicKey string
+ CreatedAt *time.Time
+ UpdatedAt *time.Time
+}
+
+func (k *PublicKey) publicKey() ssh.PublicKey {
+ pk, err := ssh.ParsePublicKey([]byte(k.PublicKey))
+ if err != nil {
+ return nil
+ }
+ return pk
+}
+
+func (k *PublicKey) String() string {
+ pk := k.publicKey()
+ if pk == nil {
+ return ""
+ }
+ return strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pk)))
+}
+
+// Type returns the type of the public key.
+func (k *PublicKey) Type() string {
+ pk := k.publicKey()
+ if pk == nil {
+ return ""
+ }
+ return pk.Type()
+}
+
+// Marshal returns the serialized form of the public key.
+func (k *PublicKey) Marshal() []byte {
+ pk := k.publicKey()
+ if pk == nil {
+ return nil
+ }
+ return pk.Marshal()
+}
+
+// Verify verifies the signature of the given data.
+func (k *PublicKey) Verify(data []byte, sig *ssh.Signature) error {
+ pk := k.publicKey()
+ if pk == nil {
+ return fmt.Errorf("invalid public key")
+ }
+ return pk.Verify(data, sig)
+}
@@ -0,0 +1,19 @@
+package types
+
+import "time"
+
+// Repo is a repository database model.
+type Repo struct {
+ ID int
+ Name string
+ ProjectName string
+ Description string
+ Private bool
+ CreatedAt *time.Time
+ UpdatedAt *time.Time
+}
+
+// String returns the name of the repository.
+func (r *Repo) String() string {
+ return r.Name
+}
@@ -0,0 +1,34 @@
+package types
+
+import (
+ "net/mail"
+ "time"
+)
+
+// User is a user database model.
+type User struct {
+ ID int
+ Name string
+ Login *string
+ Email *string
+ Password *string
+ Admin bool
+ CreatedAt *time.Time
+ UpdatedAt *time.Time
+}
+
+// Address returns the email address of the user.
+func (u *User) Address() *mail.Address {
+ if u.Email == nil {
+ return nil
+ }
+ return &mail.Address{
+ Name: u.Name,
+ Address: *u.Email,
+ }
+}
+
+// String returns the name of the user.
+func (u *User) String() string {
+ return u.Name
+}
@@ -0,0 +1,108 @@
+package file
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/charmbracelet/soft-serve/git"
+ "github.com/charmbracelet/soft-serve/proto"
+)
+
+type key int
+
+const (
+ projectName key = iota
+ description
+ private
+)
+
+var keys = map[key]string{
+ projectName: "soft-serve.projectName",
+ description: "soft-serve.description",
+ private: "soft-serve.private",
+}
+
+var _ proto.Provider = &File{}
+
+// File is a file-based repository provider.
+type File struct {
+ repoPath string
+}
+
+// New returns a new File provider.
+func New(repoPath string) *File {
+ f := &File{
+ repoPath: repoPath,
+ }
+ return f
+}
+
+// Open opens a new repository and returns a new FileRepo.
+func (f *File) Open(name string) (proto.RepositoryService, error) {
+ fp := filepath.Join(f.repoPath, name)
+ r, err := git.Open(fp)
+ if errors.Is(err, os.ErrNotExist) {
+ r, err = git.Open(fp + ".git")
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &FileRepo{r}, nil
+}
+
+var _ proto.Repository = &FileRepo{}
+
+// FileRepo is a file-based repository.
+type FileRepo struct { // nolint:revive
+ repo *git.Repository
+}
+
+// Name returns the name of the repository.
+func (r *FileRepo) Name() string {
+ return strings.TrimSuffix(r.repo.Name(), ".git")
+}
+
+// ProjectName returns the project name of the repository.
+func (r *FileRepo) ProjectName() string {
+ pn, err := r.repo.Config(keys[projectName])
+ if err != nil {
+ return ""
+ }
+ return pn
+}
+
+// SetProjectName sets the project name of the repository.
+func (r *FileRepo) SetProjectName(name string) error {
+ return r.repo.SetConfig(keys[projectName], name)
+}
+
+// Description returns the description of the repository.
+func (r *FileRepo) Description() string {
+ desc, err := r.repo.Config(keys[description])
+ if err != nil {
+ return ""
+ }
+ return desc
+}
+
+// SetDescription sets the description of the repository.
+func (r *FileRepo) SetDescription(desc string) error {
+ return r.repo.SetConfig(keys[description], desc)
+}
+
+// IsPrivate returns whether the repository is private.
+func (r *FileRepo) IsPrivate() bool {
+ p, err := r.repo.Config(keys[private])
+ if err != nil {
+ return false
+ }
+ return p == "true"
+}
+
+// SetPrivate sets whether the repository is private.
+func (r *FileRepo) SetPrivate(p bool) error {
+ return r.repo.SetConfig(keys[private], strconv.FormatBool(p))
+}
@@ -1,46 +1,16 @@
package git
-import "github.com/gliderlabs/ssh"
-
-// AccessLevel is the level of access allowed to a repo.
-type AccessLevel int
-
-const (
- // NoAccess does not allow access to the repo.
- NoAccess AccessLevel = iota
-
- // ReadOnlyAccess allows read-only access to the repo.
- ReadOnlyAccess
-
- // ReadWriteAccess allows read and write access to the repo.
- ReadWriteAccess
-
- // AdminAccess allows read, write, and admin access to the repo.
- AdminAccess
+import (
+ "github.com/charmbracelet/soft-serve/proto"
+ "github.com/gliderlabs/ssh"
)
-// String implements the Stringer interface for AccessLevel.
-func (a AccessLevel) String() string {
- switch a {
- case NoAccess:
- return "no-access"
- case ReadOnlyAccess:
- return "read-only"
- case ReadWriteAccess:
- return "read-write"
- case AdminAccess:
- return "admin-access"
- default:
- return ""
- }
-}
-
// Hooks is an interface that allows for custom authorization
// implementations and post push/fetch notifications. Prior to git access,
// AuthRepo will be called with the ssh.Session public key and the repo name.
// Implementers return the appropriate AccessLevel.
type Hooks interface {
- AuthRepo(string, ssh.PublicKey) AccessLevel
+ AuthRepo(string, ssh.PublicKey) proto.AccessLevel
Push(string, ssh.PublicKey)
Fetch(string, ssh.PublicKey)
}
@@ -12,6 +12,7 @@ import (
"sync"
"time"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/charmbracelet/soft-serve/server/config"
"github.com/charmbracelet/soft-serve/server/git"
"github.com/go-git/go-git/v5/plumbing/format/pktline"
@@ -159,7 +160,7 @@ func (d *Daemon) handleClient(c net.Conn) {
defer log.Printf("git: disconnect %s %s %s", c.RemoteAddr(), cmd, repo)
repo = strings.TrimPrefix(repo, "/")
auth := d.auth.AuthRepo(strings.TrimSuffix(repo, ".git"), nil)
- if auth < git.ReadOnlyAccess {
+ if auth < proto.ReadOnlyAccess {
fatal(c, git.ErrNotAuthed)
return
}
@@ -17,11 +17,14 @@ import (
var testDaemon *Daemon
func TestMain(m *testing.M) {
- testdata := "testdata"
- defer os.RemoveAll(testdata)
+ tmp, err := os.MkdirTemp("", "soft-serve-test")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer os.RemoveAll(tmp)
cfg := &config.Config{
Host: "",
- DataPath: testdata,
+ DataPath: tmp,
Git: config.GitConfig{
// Reduce the max timeout to 100 second so we can test the timeout.
MaxTimeout: 100,
@@ -6,6 +6,7 @@ import (
"path/filepath"
"strings"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/charmbracelet/soft-serve/server/git"
"github.com/charmbracelet/wish"
"github.com/gliderlabs/ssh"
@@ -41,7 +42,7 @@ func Middleware(repoDir string, gh git.Hooks) wish.Middleware {
switch gc {
case "git-receive-pack":
switch access {
- case git.ReadWriteAccess, git.AdminAccess:
+ case proto.ReadWriteAccess, proto.AdminAccess:
err := git.GitPack(s, s, s.Stderr(), gc, repoDir, repo)
if err != nil {
Fatal(s, git.ErrSystemMalfunction)
@@ -54,7 +55,7 @@ func Middleware(repoDir string, gh git.Hooks) wish.Middleware {
return
case "git-upload-archive", "git-upload-pack":
switch access {
- case git.ReadOnlyAccess, git.ReadWriteAccess, git.AdminAccess:
+ case proto.ReadOnlyAccess, proto.ReadWriteAccess, proto.AdminAccess:
// try to upload <repo>.git first, then <repo>
err := git.GitPack(s, s, s.Stderr(), gc, repoDir, repo)
if err != nil {
@@ -11,7 +11,7 @@ import (
"testing"
"github.com/charmbracelet/keygen"
- "github.com/charmbracelet/soft-serve/server/git"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/charmbracelet/wish"
"github.com/gliderlabs/ssh"
)
@@ -28,13 +28,13 @@ func TestGitMiddleware(t *testing.T) {
pushes: []action{},
fetches: []action{},
access: []accessDetails{
- {pubkey, "repo1", git.AdminAccess},
- {pubkey, "repo2", git.AdminAccess},
- {pubkey, "repo3", git.AdminAccess},
- {pubkey, "repo4", git.AdminAccess},
- {pubkey, "repo5", git.NoAccess},
- {pubkey, "repo6", git.ReadOnlyAccess},
- {pubkey, "repo7", git.AdminAccess},
+ {pubkey, "repo1", proto.AdminAccess},
+ {pubkey, "repo2", proto.AdminAccess},
+ {pubkey, "repo3", proto.AdminAccess},
+ {pubkey, "repo4", proto.AdminAccess},
+ {pubkey, "repo5", proto.NoAccess},
+ {pubkey, "repo6", proto.ReadOnlyAccess},
+ {pubkey, "repo7", proto.AdminAccess},
},
}
srv, err := wish.NewServer(
@@ -180,7 +180,7 @@ func createKeyPair(t *testing.T) (ssh.PublicKey, string) {
type accessDetails struct {
key ssh.PublicKey
repo string
- level git.AccessLevel
+ level proto.AccessLevel
}
type action struct {
@@ -195,13 +195,13 @@ type testHooks struct {
access []accessDetails
}
-func (h *testHooks) AuthRepo(repo string, key ssh.PublicKey) git.AccessLevel {
+func (h *testHooks) AuthRepo(repo string, key ssh.PublicKey) proto.AccessLevel {
for _, dets := range h.access {
if dets.repo == repo && ssh.KeysEqual(key, dets.key) {
return dets.level
}
}
- return git.NoAccess
+ return proto.NoAccess
}
func (h *testHooks) Push(repo string, key ssh.PublicKey) {
@@ -2,6 +2,9 @@ package server
import (
"fmt"
+ "log"
+ "net"
+ "os"
"path/filepath"
"testing"
@@ -20,9 +23,7 @@ import (
var (
cfg = &config.Config{
Host: "",
- SSH: config.SSHConfig{
- Port: 22222,
- },
+ Git: config.GitConfig{Port: 9418},
}
)
@@ -30,10 +31,8 @@ func TestPushRepo(t *testing.T) {
is := is.New(t)
_, pkPath := createKeyPair(t)
s := setupServer(t)
- defer s.Close()
err := s.Reload()
is.NoErr(err)
-
rp := t.TempDir()
r, err := git.PlainInit(rp, false)
is.NoErr(err)
@@ -55,7 +54,7 @@ func TestPushRepo(t *testing.T) {
is.NoErr(err)
_, err = r.CreateRemote(&gconfig.RemoteConfig{
Name: "origin",
- URLs: []string{fmt.Sprintf("ssh://%s:%d/%s", cfg.Host, cfg.SSH.Port, "testrepo")},
+ URLs: []string{fmt.Sprintf("ssh://localhost:%d/%s", cfg.SSH.Port, "testrepo")},
})
auth, err := gssh.NewPublicKeysFromFile("git", pkPath, "")
is.NoErr(err)
@@ -73,12 +72,13 @@ func TestCloneRepo(t *testing.T) {
is := is.New(t)
_, pkPath := createKeyPair(t)
s := setupServer(t)
- defer s.Close()
+ log.Print("starting server")
err := s.Reload()
+ log.Print("reloaded server")
is.NoErr(err)
-
dst := t.TempDir()
- url := fmt.Sprintf("ssh://%s:%d/config", cfg.Host, cfg.SSH.Port)
+ url := fmt.Sprintf("ssh://localhost:%d/config", cfg.SSH.Port)
+ log.Print("cloning repo")
err = ggit.Clone(url, dst, ggit.CloneOptions{
CommandOptions: ggit.CommandOptions{
Envs: []string{
@@ -89,16 +89,24 @@ func TestCloneRepo(t *testing.T) {
is.NoErr(err)
}
+func randomPort() int {
+ addr, _ := net.Listen("tcp", ":0") //nolint:gosec
+ _ = addr.Close()
+ return addr.Addr().(*net.TCPAddr).Port
+}
+
func setupServer(t *testing.T) *Server {
t.Helper()
- tmpdir := t.TempDir()
- cfg.DataPath = tmpdir
+ cfg.DataPath = t.TempDir()
+ cfg.SSH.Port = randomPort()
s := NewServer(cfg)
go func() {
+ log.Print("starting server")
s.Start()
}()
t.Cleanup(func() {
s.Close()
+ os.RemoveAll(cfg.DataPath)
})
return s
}
@@ -6,8 +6,8 @@ import (
"github.com/aymanbagabas/go-osc52"
tea "github.com/charmbracelet/bubbletea"
appCfg "github.com/charmbracelet/soft-serve/config"
+ "github.com/charmbracelet/soft-serve/proto"
cm "github.com/charmbracelet/soft-serve/server/cmd"
- gm "github.com/charmbracelet/soft-serve/server/git"
"github.com/charmbracelet/soft-serve/ui"
"github.com/charmbracelet/soft-serve/ui/common"
"github.com/charmbracelet/soft-serve/ui/keymap"
@@ -30,7 +30,7 @@ func SessionHandler(ac *appCfg.Config) bm.ProgramHandler {
if len(cmd) == 1 {
initialRepo = cmd[0]
auth := ac.AuthRepo(initialRepo, s.PublicKey())
- if auth < gm.ReadOnlyAccess {
+ if auth < proto.ReadOnlyAccess {
wish.Fatalln(s, cm.ErrUnauthorized)
return nil
}
@@ -55,9 +55,10 @@ func TestSession(t *testing.T) {
func setup(tb testing.TB) *gossh.Session {
is := is.New(tb)
tb.Helper()
- cfg.DataPath = tb.TempDir()
ac, err := appCfg.NewConfig(&config.Config{
- SSH: config.SSHConfig{Port: 22226},
+ Host: "",
+ SSH: config.SSHConfig{Port: randomPort()},
+ Git: config.GitConfig{Port: 9418},
DataPath: tb.TempDir(),
InitialAdminKeys: []string{
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMJlb/qf2B2kMNdBxfpCQqI2ctPcsOkdZGVh5zTRhKtH",
@@ -9,7 +9,7 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/soft-serve/config"
- gm "github.com/charmbracelet/soft-serve/server/git"
+ "github.com/charmbracelet/soft-serve/proto"
"github.com/charmbracelet/soft-serve/ui/common"
"github.com/charmbracelet/soft-serve/ui/components/code"
"github.com/charmbracelet/soft-serve/ui/components/selector"
@@ -190,7 +190,7 @@ func (s *Selection) Init() tea.Cmd {
// Put configured repos first
for _, r := range cfg.Repos {
acc := cfg.AuthRepo(r.Repo, pk)
- if r.Private && acc < gm.ReadOnlyAccess {
+ if r.Private && acc < proto.ReadOnlyAccess {
continue
}
repo, err := cfg.Source.GetRepo(r.Repo)
@@ -209,7 +209,7 @@ func (s *Selection) Init() tea.Cmd {
readmeCmd = s.readme.SetContent(rm, rp)
}
acc := cfg.AuthRepo(r.Repo(), pk)
- if r.IsPrivate() && acc < gm.ReadOnlyAccess {
+ if r.IsPrivate() && acc < proto.ReadOnlyAccess {
continue
}
exists := false