M api/go.mod => api/go.mod +1 -1
@@ 3,7 3,7 @@ module git.sr.ht/~sircmpwn/git.sr.ht/api
go 1.14
require (
- git.sr.ht/~sircmpwn/core-go v0.0.0-20201121171719-31fc9fce43e9
+ git.sr.ht/~sircmpwn/core-go v0.0.0-20201126154911-33562018fec2
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3
github.com/99designs/gqlgen v0.13.0
github.com/Masterminds/squirrel v1.4.0
M api/go.sum => api/go.sum +3 -0
@@ 2,6 2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.sr.ht/~sircmpwn/core-go v0.0.0-20201121171719-31fc9fce43e9 h1:w0toCjfdNh2JE4tes7on3pr41reASuYewcH5wGwUz+U=
git.sr.ht/~sircmpwn/core-go v0.0.0-20201121171719-31fc9fce43e9/go.mod h1:LLLvDJIgVgmA/sHl0fzj9UvpFiLi0v8a/aiBc3g22ik=
+git.sr.ht/~sircmpwn/core-go v0.0.0-20201126154911-33562018fec2 h1:qtKGxaNnO86GZtT4IYGHzQx3fN7P6NgxjhZaLeTdR84=
+git.sr.ht/~sircmpwn/core-go v0.0.0-20201126154911-33562018fec2/go.mod h1:LLLvDJIgVgmA/sHl0fzj9UvpFiLi0v8a/aiBc3g22ik=
git.sr.ht/~sircmpwn/dowork v0.0.0-20201013160733-35ca012e4dc8 h1:ltrdYYclC4wQEg3QdcG2hgYAFCk+6/l2vU1OXygKXVA=
git.sr.ht/~sircmpwn/dowork v0.0.0-20201013160733-35ca012e4dc8/go.mod h1:8neHEO3503w/rNtttnR0JFpQgM/GFhaafVwvkPsFIDw=
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3 h1:4wDp4BKF7NQqoh73VXpZsB/t1OEhDpz/zEpmdQfbjDk=
@@ 561,6 563,7 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a h1:gILuVKC+ZPD6g/tj6zBOdnOH1ZHI0zZ86+KLMogc6/s=
golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
M api/graph/api/generated.go => api/graph/api/generated.go +14 -18
@@ 1580,18 1580,30 @@ type Query {
input RepoInput {
name: String!
description: String
- visibility: Visibility
+ visibility: Visibility!
}
type Mutation {
+ # Creates a new git repository
createRepository(params: RepoInput): Repository! @access(scope: REPOSITORIES, kind: RW)
+
+ # Updates the metadata for a git repository
updateRepository(id: ID!, params: RepoInput): Repository! @access(scope: REPOSITORIES, kind: RW)
+
+ # Deletes a git repository
deleteRepository(id: ID!): Repository! @access(scope: REPOSITORIES, kind: RW)
+ # Adds or updates a user in the access control list
updateACL(repoId: ID!, mode: AccessMode!, entity: ID!): ACL! @access(scope: ACLS, kind: RW)
+
+ # Deletes an entry from the access control list
deleteACL(repoId: Int!, entity: ID!): ACL! @access(scope: ACLS, kind: RW)
+ # Uploads an artifact. revspec must match a specific git tag, and the
+ # filename must be unique among artifacts for this repository.
uploadArtifact(repoId: Int!, revspec: String!, file: Upload!): Artifact! @access(scope: OBJECTS, kind: RW)
+
+ # Deletes an artifact.
deleteArtifact(id: Int!): Artifact! @access(scope: OBJECTS, kind: RW)
}
`, BuiltIn: false},
@@ 7950,7 7962,7 @@ func (ec *executionContext) unmarshalInputRepoInput(ctx context.Context, obj int
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("visibility"))
- it.Visibility, err = ec.unmarshalOVisibility2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐVisibility(ctx, v)
+ it.Visibility, err = ec.unmarshalNVisibility2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐVisibility(ctx, v)
if err != nil {
return it, err
}
@@ 10536,22 10548,6 @@ func (ec *executionContext) marshalOUser2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗs
return ec._User(ctx, sel, v)
}
-func (ec *executionContext) unmarshalOVisibility2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐVisibility(ctx context.Context, v interface{}) (*model.Visibility, error) {
- if v == nil {
- return nil, nil
- }
- var res = new(model.Visibility)
- err := res.UnmarshalGQL(v)
- return res, graphql.ErrorOnPath(ctx, err)
-}
-
-func (ec *executionContext) marshalOVisibility2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐVisibility(ctx context.Context, sel ast.SelectionSet, v *model.Visibility) graphql.Marshaler {
- if v == nil {
- return graphql.Null
- }
- return v
-}
-
func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler {
if v == nil {
return graphql.Null
M api/graph/model/models_gen.go => api/graph/model/models_gen.go +3 -3
@@ 40,9 40,9 @@ type ReferenceCursor struct {
}
type RepoInput struct {
- Name string `json:"name"`
- Description *string `json:"description"`
- Visibility *Visibility `json:"visibility"`
+ Name string `json:"name"`
+ Description *string `json:"description"`
+ Visibility Visibility `json:"visibility"`
}
type RepositoryCursor struct {
M api/graph/resolver.go => api/graph/resolver.go +8 -0
@@ 1,5 1,13 @@
package graph
+import (
+ "regexp"
+)
+
//go:generate go run github.com/99designs/gqlgen
type Resolver struct {}
+
+var (
+ repoNameRE = regexp.MustCompile(`^[A-Za-z._-][A-Za-z0-9._-]*$`)
+)
M api/graph/schema.graphqls => api/graph/schema.graphqls +13 -1
@@ 333,17 333,29 @@ type Query {
input RepoInput {
name: String!
description: String
- visibility: Visibility
+ visibility: Visibility!
}
type Mutation {
+ # Creates a new git repository
createRepository(params: RepoInput): Repository! @access(scope: REPOSITORIES, kind: RW)
+
+ # Updates the metadata for a git repository
updateRepository(id: ID!, params: RepoInput): Repository! @access(scope: REPOSITORIES, kind: RW)
+
+ # Deletes a git repository
deleteRepository(id: ID!): Repository! @access(scope: REPOSITORIES, kind: RW)
+ # Adds or updates a user in the access control list
updateACL(repoId: ID!, mode: AccessMode!, entity: ID!): ACL! @access(scope: ACLS, kind: RW)
+
+ # Deletes an entry from the access control list
deleteACL(repoId: Int!, entity: ID!): ACL! @access(scope: ACLS, kind: RW)
+ # Uploads an artifact. revspec must match a specific git tag, and the
+ # filename must be unique among artifacts for this repository.
uploadArtifact(repoId: Int!, revspec: String!, file: Upload!): Artifact! @access(scope: OBJECTS, kind: RW)
+
+ # Deletes an artifact.
deleteArtifact(id: Int!): Artifact! @access(scope: OBJECTS, kind: RW)
}
M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +100 -1
@@ 7,7 7,10 @@ import (
"context"
"database/sql"
"fmt"
+ "os"
+ "path"
"sort"
+ "strconv"
"strings"
"git.sr.ht/~sircmpwn/core-go/auth"
@@ 54,7 57,103 @@ func (r *commitResolver) Diff(ctx context.Context, obj *model.Commit) (string, e
}
func (r *mutationResolver) CreateRepository(ctx context.Context, params *model.RepoInput) (*model.Repository, error) {
- panic(fmt.Errorf("createRepository: not implemented"))
+ if !repoNameRE.MatchString(params.Name) {
+ return nil, fmt.Errorf("Invalid repository name '%s' (must match %s)",
+ params.Name, repoNameRE.String())
+ }
+
+ conf := config.ForContext(ctx)
+ repoStore, ok := conf.Get("git.sr.ht", "repos")
+ if !ok || repoStore == "" {
+ panic(fmt.Errorf("Configuration error: [git.sr.ht]repos is unset"))
+ }
+ postUpdate, ok := conf.Get("git.sr.ht", "post-update-script")
+ if !ok {
+ panic(fmt.Errorf("Configuration error: [git.sr.ht]post-update is unset"))
+ }
+
+ user := auth.ForContext(ctx)
+ repoPath := path.Join(repoStore, "~" + user.Username, params.Name)
+
+ var (
+ repoCreated bool
+ repo model.Repository
+ )
+ if err := database.WithTx(ctx, nil, func(tx *sql.Tx) error {
+ vismap := map[model.Visibility]string{
+ model.VisibilityPublic: "public",
+ model.VisibilityUnlisted: "unlisted",
+ model.VisibilityPrivate: "private",
+ }
+ var (
+ dvis string
+ ok bool
+ )
+ if dvis, ok = vismap[params.Visibility]; !ok {
+ panic(fmt.Errorf("Unknown visibility %s", params.Visibility)) // Invariant
+ }
+
+ row := tx.QueryRowContext(ctx, `
+ INSERT INTO repository (
+ created, updated, name, description, path, visibility, owner_id
+ ) VALUES (
+ NOW() at time zone 'utc',
+ NOW() at time zone 'utc',
+ $1, $2, $3, $4, $5
+ ) RETURNING
+ id, created, updated, name, description, visibility,
+ upstream_uri, path, owner_id;
+ `, params.Name, params.Description, repoPath, dvis, user.UserID)
+ if err := row.Scan(&repo.ID, &repo.Created, &repo.Updated, &repo.Name,
+ &repo.Description, &repo.Visibility, &repo.UpstreamURL, &repo.Path,
+ &repo.OwnerID); err != nil {
+ if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
+ return fmt.Errorf("A repository with this name already exists.")
+ }
+
+ return err
+ }
+
+ gitrepo, err := git.PlainInit(repoPath, true)
+ if err != nil {
+ return err
+ }
+ repoCreated = true
+ config, err := gitrepo.Config()
+ if err != nil {
+ return err
+ }
+ config.Raw.SetOption("core", "", "repositoryformatversion", "0")
+ config.Raw.SetOption("core", "", "filemode", "true")
+ config.Raw.SetOption("srht", "", "repo-id", strconv.Itoa(repo.ID))
+ config.Raw.SetOption("receive", "", "denyDeleteCurrent", "ignore")
+ config.Raw.SetOption("receive", "", "advertisePushOptions", "true")
+ if err = gitrepo.Storer.SetConfig(config); err != nil {
+ return err
+ }
+
+ hookdir := path.Join(repoPath, "hooks")
+ if err = os.Mkdir(hookdir, os.ModePerm); err != nil {
+ return err
+ }
+ for _, hook := range []string{"pre-receive", "update", "post-update"} {
+ if err = os.Symlink(postUpdate, path.Join(hookdir, hook)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }); err != nil {
+ if repoCreated {
+ err := os.RemoveAll(repoPath)
+ if err != nil {
+ panic(err)
+ }
+ }
+ return nil, err
+ }
+
+ return &repo, nil
}
func (r *mutationResolver) UpdateRepository(ctx context.Context, id string, params *model.RepoInput) (*model.Repository, error) {
M gitsrht-shell/main.go => gitsrht-shell/main.go +1 -0
@@ 300,6 300,7 @@ func main() {
}
// Note: update gitsrht/repos.py when changing this
+ // Also update api/graph/schema.resolvers.go:CreateRepository
repo, err := git.PlainInit(path, true)
if err != nil {
notFound("git init", err)