~edwargix/git.sr.ht

3f954b33bd69f6dfec350066e6a09c000377393c — Drew DeVault 3 years ago ca90114
API: implement mutation { createRepository }
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)