diff --git a/.gitignore b/.gitignore index a1e32ca28..086666af0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,9 @@ _research *.sqlite *.sqlite3 web/node_modules -web/dist/files +web/dist +web/coverage +yarn-error* release .idea .vscode/settings.json diff --git a/README.md b/README.md index c224e0e56..cb76c67fc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -[![CI Linter pipeline](https://github.com/harness/gitness/actions/workflows/ci-lint.yml/badge.svg)](https://github.com/harness/gitness/actions/workflows/ci-lint.yml) -[![](https://img.shields.io/badge/go-%3E%3D%201.19-green)](#) # Pre-Requisites Install the latest stable version of Node and Go version 1.19 or higher, and then install the below Go programs. Ensure the GOPATH [bin directory](https://go.dev/doc/gopath_code#GOPATH) is added to your PATH. diff --git a/cache/no_cache.go b/cache/no_cache.go new file mode 100644 index 000000000..4d2002e8a --- /dev/null +++ b/cache/no_cache.go @@ -0,0 +1,27 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package cache + +import ( + "context" +) + +type NoCache[K any, V any] struct { + getter Getter[K, V] +} + +func NewNoCache[K any, V any](getter Getter[K, V]) NoCache[K, V] { + return NoCache[K, V]{ + getter: getter, + } +} + +func (c NoCache[K, V]) Stats() (int64, int64) { + return 0, 0 +} + +func (c NoCache[K, V]) Get(ctx context.Context, key K) (V, error) { + return c.getter.Find(ctx, key) +} diff --git a/cache/redis_cache.go b/cache/redis_cache.go new file mode 100644 index 000000000..8f9c154f6 --- /dev/null +++ b/cache/redis_cache.go @@ -0,0 +1,89 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package cache + +import ( + "context" + "fmt" + "time" + + "github.com/go-redis/redis/v8" +) + +type Redis[K any, V any] struct { + client redis.UniversalClient + duration time.Duration + getter Getter[K, V] + keyEncoder func(K) string + codec Codec[V] + countHit int64 + countMiss int64 +} + +type Encoder[V any] interface { + Encode(value V) string +} + +type Decoder[V any] interface { + Decode(encoded string) (V, error) +} + +type Codec[V any] interface { + Encoder[V] + Decoder[V] +} + +func NewRedis[K any, V any]( + client redis.UniversalClient, + getter Getter[K, V], + keyEncoder func(K) string, + codec Codec[V], + duration time.Duration, +) *Redis[K, V] { + return &Redis[K, V]{ + client: client, + duration: duration, + getter: getter, + keyEncoder: keyEncoder, + codec: codec, + countHit: 0, + countMiss: 0, + } +} + +// Stats returns number of cache hits and misses and can be used to monitor the cache efficiency. +func (c *Redis[K, V]) Stats() (int64, int64) { + return c.countHit, c.countMiss +} + +// Get implements the cache.Cache interface. +func (c *Redis[K, V]) Get(ctx context.Context, key K) (V, error) { + var nothing V + + strKey := c.keyEncoder(key) + + raw, err := c.client.Get(ctx, strKey).Result() + if err == nil { + c.countHit++ + return c.codec.Decode(raw) + } + if err != redis.Nil { + return nothing, err + } + + c.countMiss++ + + item, err := c.getter.Find(ctx, key) + if err != nil { + return nothing, fmt.Errorf("cache: failed to find one: %w", err) + } + + err = c.client.Set(ctx, strKey, c.codec.Encode(item), c.duration).Err() + if err != nil { + return nothing, err + } + + return item, nil +} diff --git a/cli/server/redis.go b/cli/server/redis.go index 0f6756742..717d28a6e 100644 --- a/cli/server/redis.go +++ b/cli/server/redis.go @@ -5,15 +5,31 @@ package server import ( + "strings" + "github.com/harness/gitness/types" "github.com/go-redis/redis/v8" ) // ProvideRedis provides a redis client based on the configuration. -// TODO: add support for sentinal / cluster // TODO: add support for TLS func ProvideRedis(config *types.Config) (redis.UniversalClient, error) { + if config.Redis.SentinelMode { + addrs := strings.Split(config.Redis.SentinelEndpoint, ",") + + failoverOptions := &redis.FailoverOptions{ + MasterName: config.Redis.SentinelMaster, + SentinelAddrs: addrs, + MaxRetries: config.Redis.MaxRetries, + MinIdleConns: config.Redis.MinIdleConnections, + } + if config.Redis.Password != "" { + failoverOptions.Password = config.Redis.Password + } + return redis.NewFailoverClient(failoverOptions), nil + } + options := &redis.Options{ Addr: config.Redis.Endpoint, MaxRetries: config.Redis.MaxRetries, diff --git a/cmd/gitness/wire.go b/cmd/gitness/wire.go index ddd1bd0aa..ed157dbc2 100644 --- a/cmd/gitness/wire.go +++ b/cmd/gitness/wire.go @@ -11,15 +11,19 @@ import ( "context" cliserver "github.com/harness/gitness/cli/server" + "github.com/harness/gitness/encrypt" "github.com/harness/gitness/events" "github.com/harness/gitness/gitrpc" gitrpcserver "github.com/harness/gitness/gitrpc/server" gitrpccron "github.com/harness/gitness/gitrpc/server/cron" checkcontroller "github.com/harness/gitness/internal/api/controller/check" + "github.com/harness/gitness/internal/api/controller/execution" "github.com/harness/gitness/internal/api/controller/githook" + "github.com/harness/gitness/internal/api/controller/pipeline" "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" "github.com/harness/gitness/internal/api/controller/repo" + "github.com/harness/gitness/internal/api/controller/secret" "github.com/harness/gitness/internal/api/controller/service" "github.com/harness/gitness/internal/api/controller/serviceaccount" "github.com/harness/gitness/internal/api/controller/space" @@ -79,6 +83,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e gitrpc.WireSet, store.WireSet, check.WireSet, + encrypt.WireSet, cliserver.ProvideEventsConfig, events.WireSet, cliserver.ProvideWebhookConfig, @@ -90,6 +95,9 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e codecomments.WireSet, gitrpccron.WireSet, checkcontroller.WireSet, + execution.WireSet, + pipeline.WireSet, + secret.WireSet, ) return &cliserver.System{}, nil } diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index ee8870a0f..75ef17a33 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -8,16 +8,21 @@ package main import ( "context" + "github.com/harness/gitness/cli/server" + "github.com/harness/gitness/encrypt" "github.com/harness/gitness/events" "github.com/harness/gitness/gitrpc" server3 "github.com/harness/gitness/gitrpc/server" "github.com/harness/gitness/gitrpc/server/cron" check2 "github.com/harness/gitness/internal/api/controller/check" + "github.com/harness/gitness/internal/api/controller/execution" "github.com/harness/gitness/internal/api/controller/githook" + "github.com/harness/gitness/internal/api/controller/pipeline" "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" "github.com/harness/gitness/internal/api/controller/repo" + "github.com/harness/gitness/internal/api/controller/secret" "github.com/harness/gitness/internal/api/controller/service" "github.com/harness/gitness/internal/api/controller/serviceaccount" "github.com/harness/gitness/internal/api/controller/space" @@ -84,7 +89,17 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro return nil, err } repoController := repo.ProvideController(config, db, provider, pathUID, authorizer, pathStore, repoStore, spaceStore, principalStore, gitrpcInterface) - spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, spaceStore, repoStore, principalStore, repoController, membershipStore) + executionStore := database.ProvideExecutionStore(db) + pipelineStore := database.ProvidePipelineStore(db) + executionController := execution.ProvideController(db, authorizer, executionStore, pipelineStore, spaceStore) + secretStore := database.ProvideSecretStore(db) + spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, pipelineStore, secretStore, spaceStore, repoStore, principalStore, repoController, membershipStore) + pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore, spaceStore) + encrypter, err := encrypt.ProvideEncrypter(config) + if err != nil { + return nil, err + } + secretController := secret.ProvideController(db, pathUID, pathStore, encrypter, secretStore, authorizer, spaceStore) pullReqStore := database.ProvidePullReqStore(db, principalInfoCache) pullReqActivityStore := database.ProvidePullReqActivityStore(db, principalInfoCache) codeCommentView := database.ProvideCodeCommentView(db) @@ -138,7 +153,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro principalController := principal.ProvideController(principalStore) checkStore := database.ProvideCheckStore(db, principalInfoCache) checkController := check2.ProvideController(db, authorizer, repoStore, checkStore, gitrpcInterface) - apiHandler := router.ProvideAPIHandler(config, authenticator, repoController, spaceController, pullreqController, webhookController, githookController, serviceaccountController, controller, principalController, checkController) + apiHandler := router.ProvideAPIHandler(config, authenticator, repoController, executionController, spaceController, pipelineController, secretController, pullreqController, webhookController, githookController, serviceaccountController, controller, principalController, checkController) gitHandler := router.ProvideGitHandler(config, provider, repoStore, authenticator, authorizer, gitrpcInterface) webHandler := router.ProvideWebHandler(config) routerRouter := router.ProvideRouter(config, apiHandler, gitHandler, webHandler) @@ -147,7 +162,9 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro if err != nil { return nil, err } - gitAdapter, err := server3.ProvideGITAdapter() + cacheCache := server3.ProvideGoGitRepoCache() + cache2 := server3.ProvideLastCommitCache(serverConfig, universalClient, cacheCache) + gitAdapter, err := server3.ProvideGITAdapter(cacheCache, cache2) if err != nil { return nil, err } diff --git a/encrypt/aesgcm.go b/encrypt/aesgcm.go new file mode 100644 index 000000000..4a01fbba2 --- /dev/null +++ b/encrypt/aesgcm.go @@ -0,0 +1,84 @@ +// Copyright 2023 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package encrypt + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "errors" + "io" +) + +// Aesgcm provides an encrypter that uses the aesgcm encryption +// algorithm. +type Aesgcm struct { + block cipher.Block + Compat bool +} + +// Encrypt encrypts the plaintext using aesgcm. +func (e *Aesgcm) Encrypt(plaintext string) ([]byte, error) { + gcm, err := cipher.NewGCM(e.block) + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + _, err = io.ReadFull(rand.Reader, nonce) + if err != nil { + return nil, err + } + + return gcm.Seal(nonce, nonce, []byte(plaintext), nil), nil +} + +// Decrypt decrypts the ciphertext using aesgcm. +func (e *Aesgcm) Decrypt(ciphertext []byte) (string, error) { + gcm, err := cipher.NewGCM(e.block) + if err != nil { + return "", err + } + + if len(ciphertext) < gcm.NonceSize() { + // if the decryption utility is running in compatibility + // mode, it will return the ciphertext as plain text if + // decryption fails. This should be used when running the + // database in mixed-mode, where there is a mix of encrypted + // and unencrypted content. + if e.Compat { + return string(ciphertext), nil + } + return "", errors.New("malformed ciphertext") + } + + plaintext, err := gcm.Open(nil, + ciphertext[:gcm.NonceSize()], + ciphertext[gcm.NonceSize():], + nil, + ) + // if the decryption utility is running in compatibility + // mode, it will return the ciphertext as plain text if + // decryption fails. This should be used when running the + // database in mixed-mode, where there is a mix of encrypted + // and unencrypted content. + if err != nil && e.Compat { + return string(ciphertext), nil + } + return string(plaintext), err +} + +// New provides a new aesgcm encrypter +func New(key string, compat bool) (Encrypter, error) { + if len(key) != 32 { + return nil, errKeySize + } + b := []byte(key) + block, err := aes.NewCipher(b) + if err != nil { + return nil, err + } + return &Aesgcm{block: block, Compat: compat}, nil +} diff --git a/encrypt/encrypt.go b/encrypt/encrypt.go new file mode 100644 index 000000000..adfb42454 --- /dev/null +++ b/encrypt/encrypt.go @@ -0,0 +1,20 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package encrypt + +import ( + "errors" +) + +// indicates key size is too small. +var errKeySize = errors.New("encryption key must be 32 bytes") + +// Encrypter provides field encryption and decryption. +// Encrypted values are currently limited to strings, which is +// reflected in the interface design. +type Encrypter interface { + Encrypt(plaintext string) ([]byte, error) + Decrypt(ciphertext []byte) (string, error) +} diff --git a/encrypt/none.go b/encrypt/none.go new file mode 100644 index 000000000..91897cc09 --- /dev/null +++ b/encrypt/none.go @@ -0,0 +1,19 @@ +// Copyright 2023 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package encrypt + +// none is an encryption strategy that stores secret +// values in plain text. This is the default strategy +// when no key is specified. +type none struct { +} + +func (*none) Encrypt(plaintext string) ([]byte, error) { + return []byte(plaintext), nil +} + +func (*none) Decrypt(ciphertext []byte) (string, error) { + return string(ciphertext), nil +} diff --git a/encrypt/wire.go b/encrypt/wire.go new file mode 100644 index 000000000..008717cb6 --- /dev/null +++ b/encrypt/wire.go @@ -0,0 +1,19 @@ +package encrypt + +import ( + "github.com/harness/gitness/types" + + "github.com/google/wire" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideEncrypter, +) + +func ProvideEncrypter(config *types.Config) (Encrypter, error) { + if config.Encrypter.Secret == "" { + return &none{}, nil + } + return New(config.Encrypter.Secret, config.Encrypter.MixedContent) +} diff --git a/gitrpc/branch.go b/gitrpc/branch.go index d84849bf0..ba9421d9e 100644 --- a/gitrpc/branch.go +++ b/gitrpc/branch.go @@ -10,6 +10,7 @@ import ( "fmt" "io" + "github.com/harness/gitness/gitrpc/check" "github.com/harness/gitness/gitrpc/rpc" "github.com/rs/zerolog/log" @@ -75,19 +76,24 @@ func (c *Client) CreateBranch(ctx context.Context, params *CreateBranchParams) ( if params == nil { return nil, ErrNoParamsProvided } + + if err := check.BranchName(params.BranchName); err != nil { + return nil, ErrInvalidArgumentf(err.Error()) + } + resp, err := c.refService.CreateBranch(ctx, &rpc.CreateBranchRequest{ Base: mapToRPCWriteRequest(params.WriteParams), Target: params.Target, BranchName: params.BranchName, }) if err != nil { - return nil, processRPCErrorf(err, "failed to create branch on server") + return nil, processRPCErrorf(err, "failed to create '%s' branch on server", params.BranchName) } var branch *Branch - branch, err = mapRPCBranch(resp.GetBranch()) + branch, err = mapRPCBranch(resp.Branch) if err != nil { - return nil, fmt.Errorf("failed to map rpc branch: %w", err) + return nil, processRPCErrorf(err, "failed to map rpc branch %v", resp.Branch) } return &CreateBranchOutput{ diff --git a/gitrpc/check/branch.go b/gitrpc/check/branch.go new file mode 100644 index 000000000..e5c5ca871 --- /dev/null +++ b/gitrpc/check/branch.go @@ -0,0 +1,86 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package check + +import ( + "errors" + "fmt" + "strings" +) + +/* https://git-scm.com/docs/git-check-ref-format + * How to handle various characters in refnames: + * 0: An acceptable character for refs + * 1: End-of-component + * 2: ., look for a preceding . to reject .. in refs + * 3: {, look for a preceding @ to reject @{ in refs + * 4: A bad character: ASCII control characters, and + * ":", "?", "[", "\", "^", "~", SP, or TAB + * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set + */ +var refnameDisposition = [256]byte{ + 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4, +} + +func BranchName(branch string) error { + const lock = ".lock" + last := byte('\x00') + + for i := 0; i < len(branch); i++ { + ch := branch[i] & 255 + disp := refnameDisposition[ch] + + switch disp { + case 1: + if i == 0 { + goto out + } + if last == '/' { // Refname contains "//" + return fmt.Errorf("branch '%s' cannot have two consecutive slashes // ", branch) + } + case 2: + if last == '.' { // Refname contains ".." + return fmt.Errorf("branch '%s' cannot have two consecutive dots .. ", branch) + } + case 3: + if last == '@' { // Refname contains "@{". + return fmt.Errorf("branch '%s' cannot contain a sequence @{", branch) + } + case 4: + return fmt.Errorf("branch '%s' cannot have ASCII control characters "+ + "(i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere", branch) + case 5: + return fmt.Errorf("branch '%s' can't be a pattern", branch) + } + last = ch + } +out: + if last == '\x00' { + return errors.New("branch name is empty") + } + if last == '.' { + return fmt.Errorf("branch '%s' cannot have . at the end", branch) + } + if last == '@' { + return fmt.Errorf("branch '%s' cannot be the single character @", branch) + } + if last == '/' { + return fmt.Errorf("branch '%s' cannot have / at the end", branch) + } + if branch[0] == '.' { + return fmt.Errorf("branch '%s' cannot start with '.'", branch) + } + if strings.HasSuffix(branch, lock) { + return fmt.Errorf("branch '%s' cannot end with '%s'", branch, lock) + } + return nil +} diff --git a/gitrpc/check/branch_test.go b/gitrpc/check/branch_test.go new file mode 100644 index 000000000..dfc2e941b --- /dev/null +++ b/gitrpc/check/branch_test.go @@ -0,0 +1,215 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package check + +import "testing" + +func TestBranchName(t *testing.T) { + type args struct { + branch string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "happy path", + args: args{ + branch: "new-branch", + }, + wantErr: false, + }, + { + name: "happy path, include slash", + args: args{ + branch: "eb/new-branch", + }, + wantErr: false, + }, + { + name: "happy path, test utf-8 chars", + args: args{ + branch: "eb/new\u2318branch", + }, + wantErr: false, + }, + { + name: "branch name empty should return error", + args: args{ + branch: "", + }, + wantErr: true, + }, + { + name: "branch name starts with / should return error", + args: args{ + branch: "/new-branch", + }, + wantErr: true, + }, + { + name: "branch name contains // should return error", + args: args{ + branch: "eb//new-branch", + }, + wantErr: true, + }, + { + name: "branch name ends with / should return error", + args: args{ + branch: "eb/new-branch/", + }, + wantErr: true, + }, + { + name: "branch name starts with . should return error", + args: args{ + branch: ".new-branch", + }, + wantErr: true, + }, + { + name: "branch name contains .. should return error", + args: args{ + branch: "new..branch", + }, + wantErr: true, + }, + { + name: "branch name ends with . should return error", + args: args{ + branch: "new-branch.", + }, + wantErr: true, + }, + { + name: "branch name contains ~ should return error", + args: args{ + branch: "new~branch", + }, + wantErr: true, + }, + { + name: "branch name contains ^ should return error", + args: args{ + branch: "^new-branch", + }, + wantErr: true, + }, + { + name: "branch name contains : should return error", + args: args{ + branch: "new:branch", + }, + wantErr: true, + }, + { + name: "branch name contains control char should return error", + args: args{ + branch: "new\x08branch", + }, + wantErr: true, + }, + { + name: "branch name ends with .lock should return error", + args: args{ + branch: "new-branch.lock", + }, + wantErr: true, + }, + { + name: "branch name starts with ? should return error", + args: args{ + branch: "?new-branch", + }, + wantErr: true, + }, + { + name: "branch name contains ? should return error", + args: args{ + branch: "new?branch", + }, + wantErr: true, + }, + { + name: "branch name ends with ? should return error", + args: args{ + branch: "new-branch?", + }, + wantErr: true, + }, + { + name: "branch name starts with [ should return error", + args: args{ + branch: "[new-branch", + }, + wantErr: true, + }, + { + name: "branch name contains [ should return error", + args: args{ + branch: "new[branch", + }, + wantErr: true, + }, + { + name: "branch name ends with [ should return error", + args: args{ + branch: "new-branch[", + }, + wantErr: true, + }, + { + name: "branch name starts with * should return error", + args: args{ + branch: "*new-branch", + }, + wantErr: true, + }, + { + name: "branch name contains * should return error", + args: args{ + branch: "new*branch", + }, + wantErr: true, + }, + { + name: "branch name ends with * should return error", + args: args{ + branch: "new-branch*", + }, + wantErr: true, + }, + { + name: "branch name cannot contain a sequence @{ and should return error", + args: args{ + branch: "new-br@{anch", + }, + wantErr: true, + }, + { + name: "branch name cannot be the single character @ and should return error", + args: args{ + branch: "@", + }, + wantErr: true, + }, + { + name: "branch name cannot contain \\ and should return error", + args: args{ + branch: "new-br\\anch", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := BranchName(tt.args.branch); (err != nil) != tt.wantErr { + t.Errorf("validateBranchName() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/gitrpc/commit.go b/gitrpc/commit.go index 4397e65b8..5408a560d 100644 --- a/gitrpc/commit.go +++ b/gitrpc/commit.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "strconv" "time" "github.com/harness/gitness/gitrpc/rpc" @@ -97,6 +98,7 @@ type RenameDetails struct { type ListCommitsOutput struct { Commits []Commit RenameDetails []*RenameDetails + TotalCommits int } func (c *Client) ListCommits(ctx context.Context, params *ListCommitsParams) (*ListCommitsOutput, error) { @@ -122,6 +124,21 @@ func (c *Client) ListCommits(ctx context.Context, params *ListCommitsParams) (*L Commits: make([]Commit, 0, 16), } + // check for list commits header + header, err := stream.Header() + if err != nil { + return nil, processRPCErrorf(err, "failed to read list commits header from stream") + } + + values := header.Get("total-commits") + if len(values) > 0 && values[0] != "" { + total, err := strconv.ParseInt(values[0], 10, 32) + if err != nil { + return nil, processRPCErrorf(err, "failed to convert header total-commits") + } + output.TotalCommits = int(total) + } + for { var next *rpc.ListCommitsResponse next, err = stream.Recv() diff --git a/gitrpc/errors.go b/gitrpc/errors.go index 3b2ac213d..26dc2822d 100644 --- a/gitrpc/errors.go +++ b/gitrpc/errors.go @@ -129,7 +129,7 @@ func ErrInvalidArgumentf(format string, args ...interface{}) *Error { func processRPCErrorf(err error, format string, args ...interface{}) error { // create fallback error returned if we can't map it fallbackMsg := fmt.Sprintf(format, args...) - fallbackErr := fmt.Errorf("%s: %w", fallbackMsg, err) + fallbackErr := NewError(StatusInternal, fallbackMsg) // ensure it's an rpc error st, ok := status.FromError(err) diff --git a/gitrpc/interface.go b/gitrpc/interface.go index 0752d3408..0f1b609a1 100644 --- a/gitrpc/interface.go +++ b/gitrpc/interface.go @@ -23,6 +23,7 @@ type Interface interface { DeleteBranch(ctx context.Context, params *DeleteBranchParams) error ListBranches(ctx context.Context, params *ListBranchesParams) (*ListBranchesOutput, error) GetRef(ctx context.Context, params GetRefParams) (GetRefResponse, error) + PathsDetails(ctx context.Context, params PathsDetailsParams) (PathsDetailsOutput, error) // UpdateRef creates, updates or deletes a git ref. If the OldValue is defined it must match the reference value // prior to the call. To remove a ref use the zero ref as the NewValue. To require the creation of a new one and diff --git a/gitrpc/internal/gitea/gitea.go b/gitrpc/internal/gitea/gitea.go index d41e6a317..4a9a7dc20 100644 --- a/gitrpc/internal/gitea/gitea.go +++ b/gitrpc/internal/gitea/gitea.go @@ -7,14 +7,22 @@ package gitea import ( "context" + "github.com/harness/gitness/cache" + "github.com/harness/gitness/gitrpc/internal/types" + gitea "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" ) type Adapter struct { + repoCache cache.Cache[string, *RepoEntryValue] + lastCommitCache cache.Cache[CommitEntryKey, *types.Commit] } -func New() (Adapter, error) { +func New( + repoCache cache.Cache[string, *RepoEntryValue], + lastCommitCache cache.Cache[CommitEntryKey, *types.Commit], +) (Adapter, error) { // TODO: should be subdir of gitRoot? What is it being used for? setting.Git.HomePath = "home" @@ -23,5 +31,8 @@ func New() (Adapter, error) { return Adapter{}, err } - return Adapter{}, nil + return Adapter{ + repoCache: repoCache, + lastCommitCache: lastCommitCache, + }, nil } diff --git a/gitrpc/internal/gitea/last_commit_cache.go b/gitrpc/internal/gitea/last_commit_cache.go new file mode 100644 index 000000000..0cdb484d9 --- /dev/null +++ b/gitrpc/internal/gitea/last_commit_cache.go @@ -0,0 +1,160 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package gitea + +import ( + "context" + "crypto/sha256" + "encoding/gob" + "encoding/hex" + "fmt" + "strings" + "time" + + "github.com/harness/gitness/cache" + "github.com/harness/gitness/gitrpc/internal/types" + + gitea "code.gitea.io/gitea/modules/git" + gogitplumbing "github.com/go-git/go-git/v5/plumbing" + "github.com/go-redis/redis/v8" +) + +func NewInMemoryLastCommitCache( + cacheDuration time.Duration, + repoCache cache.Cache[string, *RepoEntryValue], +) cache.Cache[CommitEntryKey, *types.Commit] { + return cache.New[CommitEntryKey, *types.Commit]( + commitEntryGetter{ + repoCache: repoCache, + }, + cacheDuration) +} + +func NewRedisLastCommitCache( + redisClient redis.UniversalClient, + cacheDuration time.Duration, + repoCache cache.Cache[string, *RepoEntryValue], +) cache.Cache[CommitEntryKey, *types.Commit] { + return cache.NewRedis[CommitEntryKey, *types.Commit]( + redisClient, + commitEntryGetter{ + repoCache: repoCache, + }, + func(key CommitEntryKey) string { + h := sha256.New() + h.Write([]byte(key)) + return "gitrpc:last_commit:" + hex.EncodeToString(h.Sum(nil)) + }, + commitValueCodec{}, + cacheDuration) +} + +func NoLastCommitCache( + repoCache cache.Cache[string, *RepoEntryValue], +) cache.Cache[CommitEntryKey, *types.Commit] { + return cache.NewNoCache[CommitEntryKey, *types.Commit](commitEntryGetter{repoCache: repoCache}) +} + +type CommitEntryKey string + +const commitEntryKeySeparator = "\x00" + +func makeCommitEntryKey(repoPath, commitSHA, path string) CommitEntryKey { + return CommitEntryKey(repoPath + commitEntryKeySeparator + commitSHA + commitEntryKeySeparator + path) +} + +func (c CommitEntryKey) Split() (repoPath, commitSHA, path string) { + parts := strings.Split(string(c), commitEntryKeySeparator) + if len(parts) != 3 { + return + } + + repoPath = parts[0] + commitSHA = parts[1] + path = parts[2] + + return +} + +type commitValueCodec struct{} + +func (c commitValueCodec) Encode(v *types.Commit) string { + buffer := &strings.Builder{} + _ = gob.NewEncoder(buffer).Encode(v) + return buffer.String() +} + +func (c commitValueCodec) Decode(s string) (*types.Commit, error) { + commit := &types.Commit{} + if err := gob.NewDecoder(strings.NewReader(s)).Decode(commit); err != nil { + return nil, fmt.Errorf("failed to unpack commit entry value: %w", err) + } + + return commit, nil +} + +type commitEntryGetter struct { + repoCache cache.Cache[string, *RepoEntryValue] +} + +// Find implements the cache.Getter interface. +func (c commitEntryGetter) Find(ctx context.Context, key CommitEntryKey) (*types.Commit, error) { + repoPath, rev, path := key.Split() + + if path == "" { + path = "." + } + + args := []string{"log", "--max-count=1", "--format=%H", rev, "--", path} + commitSHA, _, runErr := gitea.NewCommand(ctx, args...).RunStdString(&gitea.RunOpts{Dir: repoPath}) + if runErr != nil { + return nil, fmt.Errorf("failed to run git: %w", runErr) + } + + commitSHA = strings.TrimSpace(commitSHA) + + if commitSHA == "" { + return nil, types.ErrNotFound + } + + repo, err := c.repoCache.Get(ctx, repoPath) + if err != nil { + return nil, fmt.Errorf("failed to get repository %s from cache: %w", repoPath, err) + } + + commit, err := repo.Repo().CommitObject(gogitplumbing.NewHash(commitSHA)) + if err != nil { + return nil, fmt.Errorf("failed to load commit data: %w", err) + } + + var title string + var message string + + title = commit.Message + if idx := strings.IndexRune(commit.Message, '\n'); idx >= 0 { + title = commit.Message[:idx] + message = commit.Message[idx+1:] + } + + return &types.Commit{ + SHA: commitSHA, + Title: title, + Message: message, + Author: types.Signature{ + Identity: types.Identity{ + Name: commit.Author.Name, + Email: commit.Author.Email, + }, + When: commit.Author.When, + }, + Committer: types.Signature{ + Identity: types.Identity{ + Name: commit.Committer.Name, + Email: commit.Committer.Email, + }, + When: commit.Committer.When, + }, + }, nil +} diff --git a/gitrpc/internal/gitea/mapping.go b/gitrpc/internal/gitea/mapping.go index e44d1402b..cb6435c43 100644 --- a/gitrpc/internal/gitea/mapping.go +++ b/gitrpc/internal/gitea/mapping.go @@ -12,6 +12,7 @@ import ( "github.com/harness/gitness/gitrpc/internal/types" gitea "code.gitea.io/gitea/modules/git" + gogitfilemode "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/rs/zerolog/log" ) @@ -132,21 +133,21 @@ func mapGiteaCommit(giteaCommit *gitea.Commit) (*types.Commit, error) { }, nil } -func mapGiteaNodeToTreeNodeModeAndType(giteaMode gitea.EntryMode) (types.TreeNodeType, types.TreeNodeMode, error) { - switch giteaMode { - case gitea.EntryModeBlob: +func mapGogitNodeToTreeNodeModeAndType(gogitMode gogitfilemode.FileMode) (types.TreeNodeType, types.TreeNodeMode, error) { + switch gogitMode { + case gogitfilemode.Regular, gogitfilemode.Deprecated: return types.TreeNodeTypeBlob, types.TreeNodeModeFile, nil - case gitea.EntryModeSymlink: + case gogitfilemode.Symlink: return types.TreeNodeTypeBlob, types.TreeNodeModeSymlink, nil - case gitea.EntryModeExec: + case gogitfilemode.Executable: return types.TreeNodeTypeBlob, types.TreeNodeModeExec, nil - case gitea.EntryModeCommit: + case gogitfilemode.Submodule: return types.TreeNodeTypeCommit, types.TreeNodeModeCommit, nil - case gitea.EntryModeTree: + case gogitfilemode.Dir: return types.TreeNodeTypeTree, types.TreeNodeModeTree, nil default: return types.TreeNodeTypeBlob, types.TreeNodeModeFile, - fmt.Errorf("received unknown tree node mode from gitea: '%s'", giteaMode.String()) + fmt.Errorf("received unknown tree node mode from gogit: '%s'", gogitMode.String()) } } diff --git a/gitrpc/internal/gitea/paths_details.go b/gitrpc/internal/gitea/paths_details.go new file mode 100644 index 000000000..b11e60d86 --- /dev/null +++ b/gitrpc/internal/gitea/paths_details.go @@ -0,0 +1,82 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package gitea + +import ( + "context" + "errors" + "fmt" + + "github.com/harness/gitness/gitrpc/internal/types" + + gogitplumbing "github.com/go-git/go-git/v5/plumbing" + gogitfilemode "github.com/go-git/go-git/v5/plumbing/filemode" + gogitobject "github.com/go-git/go-git/v5/plumbing/object" +) + +// PathsDetails returns additional details about provided the paths. +func (g Adapter) PathsDetails(ctx context.Context, + repoPath string, + rev string, + paths []string, +) ([]types.PathDetails, error) { + repoEntry, err := g.repoCache.Get(ctx, repoPath) + if err != nil { + return nil, fmt.Errorf("failed to open repository: %w", err) + } + + repo := repoEntry.Repo() + + refSHA, err := repo.ResolveRevision(gogitplumbing.Revision(rev)) + if errors.Is(err, gogitplumbing.ErrReferenceNotFound) { + return nil, types.ErrNotFound + } else if err != nil { + return nil, fmt.Errorf("failed to resolve revision %s: %w", rev, err) + } + + refCommit, err := repo.CommitObject(*refSHA) + if err != nil { + return nil, fmt.Errorf("failed to load commit data: %w", err) + } + + tree, err := refCommit.Tree() + if err != nil { + return nil, fmt.Errorf("failed to get tree for the commit: %w", err) + } + + results := make([]types.PathDetails, len(paths)) + + for i, path := range paths { + results[i].Path = path + + if len(path) > 0 { + entry, err := tree.FindEntry(path) + if errors.Is(err, gogitobject.ErrDirectoryNotFound) || errors.Is(err, gogitobject.ErrEntryNotFound) { + return nil, types.ErrPathNotFound + } else if err != nil { + return nil, fmt.Errorf("can't find path entry %s: %w", path, err) + } + + if entry.Mode == gogitfilemode.Regular || entry.Mode == gogitfilemode.Executable { + blobObj, err := repo.Object(gogitplumbing.BlobObject, entry.Hash) + if err != nil { + return nil, fmt.Errorf("failed to get blob object size for the path %s and hash %s: %w", + path, entry.Hash.String(), err) + } + + results[i].Size = blobObj.(*gogitobject.Blob).Size + } + } + + commitEntry, err := g.lastCommitCache.Get(ctx, makeCommitEntryKey(repoPath, refSHA.String(), path)) + if err != nil { + return nil, fmt.Errorf("failed to find last commit for path %s: %w", path, err) + } + + results[i].LastCommit = commitEntry + } + + return results, nil +} diff --git a/gitrpc/internal/gitea/repo_cache.go b/gitrpc/internal/gitea/repo_cache.go new file mode 100644 index 000000000..a9dc1da24 --- /dev/null +++ b/gitrpc/internal/gitea/repo_cache.go @@ -0,0 +1,35 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package gitea + +import ( + "context" + "time" + + "github.com/harness/gitness/cache" + + gogit "github.com/go-git/go-git/v5" +) + +func NewRepoCache() cache.Cache[string, *RepoEntryValue] { + return cache.New[string, *RepoEntryValue](repoGetter{}, 4*time.Hour) +} + +type repoGetter struct{} + +type RepoEntryValue gogit.Repository + +func (repo *RepoEntryValue) Repo() *gogit.Repository { + return (*gogit.Repository)(repo) +} + +func (r repoGetter) Find(_ context.Context, path string) (*RepoEntryValue, error) { + repo, err := gogit.PlainOpen(path) + if err != nil { + return nil, err + } + + return (*RepoEntryValue)(repo), nil +} diff --git a/gitrpc/internal/gitea/tree.go b/gitrpc/internal/gitea/tree.go index e0bbea392..e56514f1c 100644 --- a/gitrpc/internal/gitea/tree.go +++ b/gitrpc/internal/gitea/tree.go @@ -7,6 +7,7 @@ package gitea import ( "bytes" "context" + "errors" "fmt" "io" "path" @@ -16,6 +17,9 @@ import ( "github.com/harness/gitness/gitrpc/internal/types" gitea "code.gitea.io/gitea/modules/git" + gogitplumbing "github.com/go-git/go-git/v5/plumbing" + gogitfilemode "github.com/go-git/go-git/v5/plumbing/filemode" + gogitobject "github.com/go-git/go-git/v5/plumbing/object" ) func cleanTreePath(treePath string) string { @@ -24,30 +28,53 @@ func cleanTreePath(treePath string) string { // GetTreeNode returns the tree node at the given path as found for the provided reference. // Note: ref can be Branch / Tag / CommitSHA. -func (g Adapter) GetTreeNode(ctx context.Context, repoPath string, - ref string, treePath string) (*types.TreeNode, error) { +func (g Adapter) GetTreeNode(ctx context.Context, + repoPath string, + ref string, + treePath string, +) (*types.TreeNode, error) { treePath = cleanTreePath(treePath) - giteaRepo, err := gitea.OpenRepository(ctx, repoPath) + repoEntry, err := g.repoCache.Get(ctx, repoPath) if err != nil { return nil, processGiteaErrorf(err, "failed to open repository") } - defer giteaRepo.Close() - // Get the giteaCommit object for the ref - giteaCommit, err := giteaRepo.GetCommit(ref) - if err != nil { - return nil, processGiteaErrorf(err, "error getting commit for ref '%s'", ref) + repo := repoEntry.Repo() + + refSHA, err := repo.ResolveRevision(gogitplumbing.Revision(ref)) + if errors.Is(err, gogitplumbing.ErrReferenceNotFound) { + return nil, types.ErrNotFound + } else if err != nil { + return nil, fmt.Errorf("failed to resolve revision %s: %w", ref, err) } - // TODO: handle ErrNotExist :) - giteaTreeEntry, err := giteaCommit.GetTreeEntryByPath(treePath) + refCommit, err := repo.CommitObject(*refSHA) if err != nil { - return nil, processGiteaErrorf(err, "failed to get tree entry for commit '%s' at path '%s'", - giteaCommit.ID.String(), treePath) + return nil, fmt.Errorf("failed to load commit data: %w", err) } - nodeType, mode, err := mapGiteaNodeToTreeNodeModeAndType(giteaTreeEntry.Mode()) + rootEntry := gogitobject.TreeEntry{ + Name: "", + Mode: gogitfilemode.Dir, + Hash: refCommit.TreeHash, + } + + treeEntry := &rootEntry + + if len(treePath) > 0 { + tree, err := refCommit.Tree() + if err != nil { + return nil, fmt.Errorf("failed to get tree for the commit: %w", err) + } + + treeEntry, err = tree.FindEntry(treePath) + if err != nil { + return nil, fmt.Errorf("can't find path entry %s: %w", treePath, types.ErrPathNotFound) + } + } + + nodeType, mode, err := mapGogitNodeToTreeNodeModeAndType(treeEntry.Mode) if err != nil { return nil, err } @@ -55,106 +82,74 @@ func (g Adapter) GetTreeNode(ctx context.Context, repoPath string, return &types.TreeNode{ Mode: mode, NodeType: nodeType, - Sha: giteaTreeEntry.ID.String(), - Name: giteaTreeEntry.Name(), + Sha: treeEntry.Hash.String(), + Name: treeEntry.Name, Path: treePath, }, nil } // ListTreeNodes lists the child nodes of a tree reachable from ref via the specified path // and includes the latest commit for all nodes if requested. -// IMPORTANT: recursive and includeLatestCommit can't be used together. // Note: ref can be Branch / Tag / CommitSHA. // //nolint:gocognit // refactor if needed -func (g Adapter) ListTreeNodes(ctx context.Context, repoPath string, - ref string, treePath string, recursive bool, includeLatestCommit bool) ([]types.TreeNodeWithCommit, error) { - if recursive && includeLatestCommit { - // To avoid potential performance catastrophe, block recursive with includeLatestCommit - // TODO: this should return bad error to caller if needed? - // TODO: should this be refactored in two methods? - return nil, fmt.Errorf("latest commit with recursive query is not supported") - } - +func (g Adapter) ListTreeNodes(ctx context.Context, + repoPath string, + ref string, + treePath string, +) ([]types.TreeNode, error) { treePath = cleanTreePath(treePath) - giteaRepo, err := gitea.OpenRepository(ctx, repoPath) + repoEntry, err := g.repoCache.Get(ctx, repoPath) if err != nil { return nil, processGiteaErrorf(err, "failed to open repository") } - defer giteaRepo.Close() - // Get the giteaCommit object for the ref - giteaCommit, err := giteaRepo.GetCommit(ref) + repo := repoEntry.Repo() + + refSHA, err := repo.ResolveRevision(gogitplumbing.Revision(ref)) + if errors.Is(err, gogitplumbing.ErrReferenceNotFound) { + return nil, types.ErrNotFound + } else if err != nil { + return nil, fmt.Errorf("failed to resolve revision %s: %w", ref, err) + } + + refCommit, err := repo.CommitObject(*refSHA) if err != nil { - return nil, processGiteaErrorf(err, "error getting commit for ref '%s'", ref) + return nil, fmt.Errorf("failed to load commit data: %w", err) } - // Get the giteaTree object for the ref - giteaTree, err := giteaCommit.SubTree(treePath) + tree, err := refCommit.Tree() if err != nil { - return nil, processGiteaErrorf(err, "error getting tree for '%s'", treePath) + return nil, fmt.Errorf("failed to get tree for the commit: %w", err) } - var giteaEntries gitea.Entries - if recursive { - giteaEntries, err = giteaTree.ListEntriesRecursive() - } else { - giteaEntries, err = giteaTree.ListEntries() - } - if err != nil { - return nil, processGiteaErrorf(err, "failed to list entries for tree '%s'", treePath) - } - - var latestCommits []gitea.CommitInfo - if includeLatestCommit { - // TODO: can be speed up with latestCommitCache (currently nil) - latestCommits, _, err = giteaEntries.GetCommitsInfo(ctx, giteaCommit, treePath, nil) - if err != nil { - return nil, processGiteaErrorf(err, "failed to get latest commits for entries") - } - - if len(latestCommits) != len(giteaEntries) { - return nil, fmt.Errorf("latest commit info doesn't match tree node info - count differs") + if len(treePath) > 0 { + tree, err = tree.Tree(treePath) + if errors.Is(err, gogitobject.ErrDirectoryNotFound) || errors.Is(err, gogitobject.ErrEntryNotFound) { + return nil, types.ErrPathNotFound + } else if err != nil { + return nil, fmt.Errorf("can't find path entry %s: %w", treePath, err) } } - nodes := make([]types.TreeNodeWithCommit, len(giteaEntries)) - for i := range giteaEntries { - giteaEntry := giteaEntries[i] - - var nodeType types.TreeNodeType - var mode types.TreeNodeMode - nodeType, mode, err = mapGiteaNodeToTreeNodeModeAndType(giteaEntry.Mode()) + treeNodes := make([]types.TreeNode, len(tree.Entries)) + for i, treeEntry := range tree.Entries { + nodeType, mode, err := mapGogitNodeToTreeNodeModeAndType(treeEntry.Mode) if err != nil { return nil, err } - // giteaNode.Name() returns the path of the node relative to the tree. - relPath := giteaEntry.Name() - name := filepath.Base(relPath) - - var commit *types.Commit - if includeLatestCommit { - commit, err = mapGiteaCommit(latestCommits[i].Commit) - if err != nil { - return nil, err - } - } - - nodes[i] = types.TreeNodeWithCommit{ - TreeNode: types.TreeNode{ - NodeType: nodeType, - Mode: mode, - Sha: giteaEntry.ID.String(), - Name: name, - Path: filepath.Join(treePath, relPath), - }, - Commit: commit, + treeNodes[i] = types.TreeNode{ + NodeType: nodeType, + Mode: mode, + Sha: treeEntry.Hash.String(), + Name: treeEntry.Name, + Path: filepath.Join(treePath, treeEntry.Name), } } - return nodes, nil + return treeNodes, nil } func (g Adapter) ReadTree(ctx context.Context, repoPath, ref string, w io.Writer, args ...string) error { diff --git a/gitrpc/internal/service/branch.go b/gitrpc/internal/service/branch.go index 91da500ba..e2bec8df0 100644 --- a/gitrpc/internal/service/branch.go +++ b/gitrpc/internal/service/branch.go @@ -9,6 +9,7 @@ import ( "fmt" "strings" + "github.com/harness/gitness/gitrpc/check" "github.com/harness/gitness/gitrpc/internal/gitea" "github.com/harness/gitness/gitrpc/internal/types" "github.com/harness/gitness/gitrpc/rpc" @@ -21,8 +22,14 @@ import ( var listBranchesRefFields = []types.GitReferenceField{types.GitReferenceFieldRefName, types.GitReferenceFieldObjectName} -func (s ReferenceService) CreateBranch(ctx context.Context, - request *rpc.CreateBranchRequest) (*rpc.CreateBranchResponse, error) { +func (s ReferenceService) CreateBranch( + ctx context.Context, + request *rpc.CreateBranchRequest, +) (*rpc.CreateBranchResponse, error) { + if err := check.BranchName(request.BranchName); err != nil { + return nil, ErrInvalidArgument(err) + } + base := request.GetBase() if base == nil { return nil, types.ErrBaseCannotBeEmpty @@ -58,7 +65,7 @@ func (s ReferenceService) CreateBranch(ctx context.Context, return nil, ErrAlreadyExistsf("branch '%s' already exists", request.GetBranchName()) } if !git.IsErrNotExist(err) { - return nil, fmt.Errorf("branch creation of '%s' failed: %w", request.GetBranchName(), err) + return nil, processGitErrorf(err, "branch creation of '%s' failed", request.GetBranchName()) } // get target commit (as target could be branch/tag/commit, and tag can't be pushed using source:destination syntax) @@ -67,7 +74,7 @@ func (s ReferenceService) CreateBranch(ctx context.Context, return nil, ErrNotFoundf("target '%s' doesn't exist", request.GetTarget()) } if err != nil { - return nil, fmt.Errorf("failed to get commit id for target '%s': %w", request.GetTarget(), err) + return nil, processGitErrorf(err, "failed to get commit id for target '%s'", request.GetTarget()) } // push to new branch (all changes should go through push flow for hooks and other safety meassures) diff --git a/gitrpc/internal/service/commit.go b/gitrpc/internal/service/commit.go index 6a5e96aba..7d967b84b 100644 --- a/gitrpc/internal/service/commit.go +++ b/gitrpc/internal/service/commit.go @@ -6,12 +6,14 @@ package service import ( "context" + "strconv" "github.com/harness/gitness/gitrpc/internal/types" "github.com/harness/gitness/gitrpc/rpc" "github.com/rs/zerolog/log" "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) @@ -65,7 +67,27 @@ func (s RepositoryService) ListCommits(request *rpc.ListCommitsRequest, return processGitErrorf(err, "failed to get list of commits") } + // try to get total commits between gitref and After refs + totalCommits := 0 + if request.Page == 1 && len(gitCommits) < int(request.Limit) { + totalCommits = len(gitCommits) + } else if request.After != "" && request.GitRef != request.After { + div, err := s.adapter.GetCommitDivergences(ctx, repoPath, []types.CommitDivergenceRequest{ + {From: request.GitRef, To: request.After}, + }, 0) + if err != nil { + return processGitErrorf(err, "failed to get total commits") + } + if len(div) > 0 { + totalCommits = int(div[0].Ahead) + } + } + log.Ctx(ctx).Trace().Msgf("git adapter returned %d commits", len(gitCommits)) + header := metadata.New(map[string]string{"total-commits": strconv.Itoa(totalCommits)}) + if err := stream.SendHeader(header); err != nil { + return ErrInternalf("unable to send 'total-commits' header", err) + } for i := range gitCommits { var commit *rpc.Commit @@ -86,16 +108,6 @@ func (s RepositoryService) ListCommits(request *rpc.ListCommitsRequest, return nil } -func (s RepositoryService) getLatestCommit(ctx context.Context, repoPath string, - ref string, path string) (*rpc.Commit, error) { - gitCommit, err := s.adapter.GetLatestCommit(ctx, repoPath, ref, path) - if err != nil { - return nil, processGitErrorf(err, "failed to get latest commit") - } - - return mapGitCommit(gitCommit) -} - func (s RepositoryService) GetCommitDivergences(ctx context.Context, request *rpc.GetCommitDivergencesRequest) (*rpc.GetCommitDivergencesResponse, error) { base := request.GetBase() diff --git a/gitrpc/internal/service/errors.go b/gitrpc/internal/service/errors.go index 27c3097f9..c26606774 100644 --- a/gitrpc/internal/service/errors.go +++ b/gitrpc/internal/service/errors.go @@ -199,7 +199,8 @@ func processGitErrorf(err error, format string, args ...interface{}) error { switch { case errors.Is(err, types.ErrNotFound), errors.Is(err, types.ErrSHADoesNotMatch), - errors.Is(err, types.ErrHunkNotFound): + errors.Is(err, types.ErrHunkNotFound), + errors.Is(err, types.ErrPathNotFound): return ErrNotFoundf(format, args...) case errors.Is(err, types.ErrAlreadyExists): return ErrAlreadyExistsf(format, args...) @@ -217,6 +218,6 @@ func processGitErrorf(err error, format string, args ...interface{}) error { case errors.Is(err, types.ErrFailedToConnect): return ErrInvalidArgumentf(format, args...) default: - return Errorf(codes.Unknown, format, args...) + return ErrInternalf(format, args...) } } diff --git a/gitrpc/internal/service/interface.go b/gitrpc/internal/service/interface.go index da0bf7723..255ab68b9 100644 --- a/gitrpc/internal/service/interface.go +++ b/gitrpc/internal/service/interface.go @@ -25,8 +25,8 @@ type GitAdapter interface { Push(ctx context.Context, repoPath string, opts types.PushOptions) error ReadTree(ctx context.Context, repoPath, ref string, w io.Writer, args ...string) error GetTreeNode(ctx context.Context, repoPath string, ref string, treePath string) (*types.TreeNode, error) - ListTreeNodes(ctx context.Context, repoPath string, ref string, treePath string, - recursive bool, includeLatestCommit bool) ([]types.TreeNodeWithCommit, error) + ListTreeNodes(ctx context.Context, repoPath string, ref string, treePath string) ([]types.TreeNode, error) + PathsDetails(ctx context.Context, repoPath string, ref string, paths []string) ([]types.PathDetails, error) GetSubmodule(ctx context.Context, repoPath string, ref string, treePath string) (*types.Submodule, error) GetBlob(ctx context.Context, repoPath string, sha string, sizeLimit int64) (*types.BlobReader, error) WalkReferences(ctx context.Context, repoPath string, handler types.WalkReferencesHandler, diff --git a/gitrpc/internal/service/operations.go b/gitrpc/internal/service/operations.go index 7803d0b18..86889bfec 100644 --- a/gitrpc/internal/service/operations.go +++ b/gitrpc/internal/service/operations.go @@ -55,17 +55,17 @@ func (s *CommitFilesService) CommitFiles(stream rpc.CommitFilesService_CommitFil ctx := stream.Context() headerRequest, err := stream.Recv() if err != nil { - return err + return ErrInternal(err) } header := headerRequest.GetHeader() if header == nil { - return types.ErrHeaderCannotBeEmpty + return ErrInvalidArgument(types.ErrHeaderCannotBeEmpty) } base := header.GetBase() if base == nil { - return types.ErrBaseCannotBeEmpty + return ErrInvalidArgument(types.ErrBaseCannotBeEmpty) } committer := base.GetActor() @@ -101,7 +101,7 @@ func (s *CommitFilesService) CommitFiles(stream rpc.CommitFilesService_CommitFil // If the user wants to actually build a disconnected commit graph they can use the cli. isEmpty, err := repoHasBranches(ctx, repo) if err != nil { - return fmt.Errorf("failed to determine if repo is empty: %w", err) + return ErrInternalf("failed to determine if repository is empty", err) } // ensure input data is valid @@ -118,7 +118,7 @@ func (s *CommitFilesService) CommitFiles(stream rpc.CommitFilesService_CommitFil // create a new shared repo shared, err := NewSharedRepo(s.reposTempDir, base.GetRepoUid(), repo) if err != nil { - return err + return processGitErrorf(err, "failed to create shared repository") } defer shared.Close(ctx) @@ -139,7 +139,7 @@ func (s *CommitFilesService) CommitFiles(stream rpc.CommitFilesService_CommitFil // Now write the tree treeHash, err := shared.WriteTree(ctx) if err != nil { - return err + return processGitErrorf(err, "failed to write tree object") } message := strings.TrimSpace(header.GetTitle()) @@ -159,16 +159,16 @@ func (s *CommitFilesService) CommitFiles(stream rpc.CommitFilesService_CommitFil committerDate, ) if err != nil { - return err + return processGitErrorf(err, "failed to commit the tree") } if err = shared.PushCommitToBranch(ctx, base, commitSHA, header.GetNewBranchName()); err != nil { - return err + return processGitErrorf(err, "failed to push commits to remote repository") } commit, err := shared.GetCommit(commitSHA) if err != nil { - return err + return processGitErrorf(err, "failed to get commit for SHA %s", commitSHA) } return stream.SendAndClose(&rpc.CommitFilesResponse{ @@ -186,7 +186,7 @@ func (s *CommitFilesService) prepareTree(ctx context.Context, shared *SharedRepo // Get the latest commit of the original branch commit, err := shared.GetBranchCommit(branchName) if err != nil { - return "", err + return "", processGitErrorf(err, "failed to get latest commit of the branch %s", branchName) } // execute all actions @@ -205,22 +205,22 @@ func (s *CommitFilesService) prepareTreeEmptyRepo(ctx context.Context, shared *S // init a new repo (full clone would cause risk that by time of push someone wrote to the remote repo!) err := shared.Init(ctx) if err != nil { - return fmt.Errorf("failed to init shared tmp repo: %w", err) + return processGitErrorf(err, "failed to init shared tmp repository") } for _, action := range actions { if action.header.Action != rpc.CommitFilesActionHeader_CREATE { - return types.ErrActionNotAllowedOnEmptyRepo + return ErrFailedPrecondition(types.ErrActionNotAllowedOnEmptyRepo) } filePath := files.CleanUploadFileName(action.header.GetPath()) if filePath == "" { - return types.ErrInvalidPath + return ErrInvalidArgument(types.ErrInvalidPath) } reader := bytes.NewReader(action.content) if err = createFile(ctx, shared, nil, filePath, defaultFilePermission, reader); err != nil { - return fmt.Errorf("failed to create file '%s': %w", action.header.Path, err) + return ErrInternalf("failed to create file '%s'", action.header.Path, err) } } @@ -232,7 +232,7 @@ func (s *CommitFilesService) validateAndPrepareHeader(repo *git.Repository, isEm if header.GetBranchName() == "" { defaultBranchRef, err := repo.GetDefaultBranch() if err != nil { - return err + return processGitErrorf(err, "failed to get default branch") } header.BranchName = defaultBranchRef } @@ -252,17 +252,17 @@ func (s *CommitFilesService) validateAndPrepareHeader(repo *git.Repository, isEm // ensure source branch exists if _, err := repo.GetBranch(header.GetBranchName()); err != nil { - return err + return processGitErrorf(err, "failed to get source branch %s", header.BranchName) } // ensure new branch doesn't exist yet (if new branch creation was requested) if header.GetBranchName() != header.GetNewBranchName() { existingBranch, err := repo.GetBranch(header.GetNewBranchName()) if existingBranch != nil { - return fmt.Errorf("branch %s %w", existingBranch.Name, types.ErrAlreadyExists) + return ErrAlreadyExistsf("branch %s already exists", existingBranch.Name) } if err != nil && !git.IsErrBranchNotExist(err) { - return err + return processGitErrorf(err, "failed to create new branch %s", header.NewBranchName) } } return nil @@ -274,11 +274,11 @@ func (s *CommitFilesService) clone( branch string, ) error { if err := shared.Clone(ctx, branch); err != nil { - return fmt.Errorf("failed to clone branch '%s': %w", branch, err) + return ErrInternalf("failed to clone branch '%s'", branch, err) } if err := shared.SetDefaultIndex(ctx); err != nil { - return fmt.Errorf("failed to set default index: %w", err) + return ErrInternalf("failed to set default index", err) } return nil @@ -299,7 +299,7 @@ func (s *CommitFilesService) collectActions( break } - return fmt.Errorf("receive request: %w", err) + return ErrInternalf("receive request failed", err) } switch payload := req.GetAction().GetPayload().(type) { @@ -307,18 +307,18 @@ func (s *CommitFilesService) collectActions( actions = append(actions, fileAction{header: payload.Header}) case *rpc.CommitFilesAction_Content: if len(actions) == 0 { - return types.ErrContentSentBeforeAction + return ErrFailedPrecondition(types.ErrContentSentBeforeAction) } // append the content to the previous fileAction content := &actions[len(actions)-1].content *content = append(*content, payload.Content...) default: - return fmt.Errorf("unhandled fileAction payload type: %T", payload) + return ErrInternalf("unhandled fileAction payload type: %T", payload) } } if len(actions) == 0 { - return types.ErrActionListEmpty + return ErrInvalidArgument(types.ErrActionListEmpty) } *ptrActions = actions return nil @@ -330,20 +330,14 @@ func (s *CommitFilesService) processAction( action *fileAction, commit *git.Commit, ) (err error) { - defer func() { - if err != nil { - err = fmt.Errorf("in processActions: %w", err) - } - }() - header := action.header if _, ok := rpc.CommitFilesActionHeader_ActionType_name[int32(header.Action)]; !ok { - return fmt.Errorf("%s %w", action.header.Action, types.ErrUndefinedAction) + return ErrInvalidArgumentf("undefined file action %s", action.header.Action, types.ErrUndefinedAction) } filePath := files.CleanUploadFileName(header.GetPath()) if filePath == "" { - return types.ErrInvalidPath + return ErrInvalidArgument(types.ErrInvalidPath) } reader := bytes.NewReader(action.content) @@ -373,12 +367,12 @@ func createFile(ctx context.Context, repo *SharedRepo, commit *git.Commit, hash, err := repo.WriteGitObject(ctx, reader) if err != nil { - return fmt.Errorf("error hashing object %w", err) + return processGitErrorf(err, "error hashing object") } // Add the object to the index if err = repo.AddObjectToIndex(ctx, mode, hash, filePath); err != nil { - return fmt.Errorf("error creating object: %w", err) + return processGitErrorf(err, "error creating object") } return nil } @@ -388,7 +382,7 @@ func updateFile(ctx context.Context, repo *SharedRepo, commit *git.Commit, fileP // get file mode from existing file (default unless executable) entry, err := getFileEntry(commit, sha, filePath) if err != nil { - return fmt.Errorf("failed to get file entry: %w", err) + return err } if entry.IsExecutable() { mode = "100755" @@ -396,11 +390,11 @@ func updateFile(ctx context.Context, repo *SharedRepo, commit *git.Commit, fileP hash, err := repo.WriteGitObject(ctx, reader) if err != nil { - return fmt.Errorf("error hashing object %w", err) + return processGitErrorf(err, "error hashing object") } if err = repo.AddObjectToIndex(ctx, mode, hash, filePath); err != nil { - return fmt.Errorf("error updating object: %w", err) + return processGitErrorf(err, "error updating object") } return nil } @@ -416,7 +410,7 @@ func moveFile(ctx context.Context, repo *SharedRepo, commit *git.Commit, if buffer.Len() == 0 && newPath != "" { err = repo.ShowFile(ctx, filePath, commit.ID.String(), buffer) if err != nil { - return err + return processGitErrorf(err, "failed lookup for path %s", newPath) } } @@ -426,23 +420,23 @@ func moveFile(ctx context.Context, repo *SharedRepo, commit *git.Commit, filesInIndex, err := repo.LsFiles(ctx, filePath) if err != nil { - return fmt.Errorf("listing files error %w", err) + return processGitErrorf(err, "listing files error") } if !slices.Contains(filesInIndex, filePath) { - return fmt.Errorf("%s %w", filePath, types.ErrNotFound) + return ErrNotFoundf("path %s not found", filePath) } hash, err := repo.WriteGitObject(ctx, buffer) if err != nil { - return fmt.Errorf("error hashing object %w", err) + return processGitErrorf(err, "error hashing object") } if err = repo.AddObjectToIndex(ctx, mode, hash, newPath); err != nil { - return fmt.Errorf("add object: %w", err) + return processGitErrorf(err, "add object error") } if err = repo.RemoveFilesFromIndex(ctx, filePath); err != nil { - return fmt.Errorf("remove object: %w", err) + return processGitErrorf(err, "remove object error") } return nil } @@ -450,14 +444,14 @@ func moveFile(ctx context.Context, repo *SharedRepo, commit *git.Commit, func deleteFile(ctx context.Context, repo *SharedRepo, filePath string) error { filesInIndex, err := repo.LsFiles(ctx, filePath) if err != nil { - return fmt.Errorf("listing files error %w", err) + return processGitErrorf(err, "listing files error") } if !slices.Contains(filesInIndex, filePath) { - return fmt.Errorf("%s %w", filePath, types.ErrNotFound) + return ErrNotFoundf("file path %s not found", filePath) } if err = repo.RemoveFilesFromIndex(ctx, filePath); err != nil { - return fmt.Errorf("remove object: %w", err) + return processGitErrorf(err, "remove object error") } return nil } @@ -469,16 +463,16 @@ func getFileEntry( ) (*git.TreeEntry, error) { entry, err := commit.GetTreeEntryByPath(path) if git.IsErrNotExist(err) { - return nil, fmt.Errorf("%s %w", path, types.ErrNotFound) + return nil, ErrNotFoundf("path %s not found", path) } if err != nil { - return nil, err + return nil, processGitErrorf(err, "failed to get tree for path %s", path) } // If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error if sha == "" || sha != entry.ID.String() { - return nil, fmt.Errorf("%w for path %s [given: %s, expected: %s]", - types.ErrSHADoesNotMatch, path, sha, entry.ID.String()) + return nil, ErrInvalidArgumentf("sha does not match for path %s [given: %s, expected: %s]", + path, sha, entry.ID.String()) } return entry, nil @@ -500,22 +494,22 @@ func checkPathAvailability(commit *git.Commit, filePath string, isNewFile bool) // Means there is no item with that name, so we're good break } - return err + return processGitErrorf(err, "failed to get tree entry for path %s", subTreePath) } switch { case index < len(parts)-1: if !entry.IsDir() { - return fmt.Errorf("a file %w where you're trying to create a subdirectory [path: %s]", - types.ErrAlreadyExists, subTreePath) + return ErrAlreadyExistsf("a file already exists where you're trying to create a subdirectory [path: %s]", + subTreePath) } case entry.IsLink(): return fmt.Errorf("a symbolic link %w where you're trying to create a subdirectory [path: %s]", types.ErrAlreadyExists, subTreePath) case entry.IsDir(): - return fmt.Errorf("a directory %w where you're trying to create a subdirectory [path: %s]", - types.ErrAlreadyExists, subTreePath) + return ErrAlreadyExistsf("a directory already exists where you're trying to create a subdirectory [path: %s]", + subTreePath) case filePath != "" || isNewFile: - return fmt.Errorf("%s %w", filePath, types.ErrAlreadyExists) + return ErrAlreadyExistsf("file path %s already exists", filePath) } } return nil @@ -530,7 +524,7 @@ func repoHasBranches(ctx context.Context, repo *git.Repository) (bool, error) { stdout, _, runErr := git.NewCommand(ctx, "rev-list", "--max-count", "1", "--branches"). RunStdBytes(&git.RunOpts{Dir: repo.Path}) if runErr != nil { - return false, fmt.Errorf("failed to trigger rev-list command: %w", runErr) + return false, processGitErrorf(runErr, "failed to trigger rev-list command") } return strings.TrimSpace(string(stdout)) == "", nil diff --git a/gitrpc/internal/service/tree.go b/gitrpc/internal/service/tree.go index 0ec534057..fc4dccccc 100644 --- a/gitrpc/internal/service/tree.go +++ b/gitrpc/internal/service/tree.go @@ -6,6 +6,7 @@ package service import ( "context" + "fmt" "github.com/harness/gitness/gitrpc/internal/types" "github.com/harness/gitness/gitrpc/rpc" @@ -15,8 +16,10 @@ import ( "google.golang.org/grpc/status" ) -func (s RepositoryService) ListTreeNodes(request *rpc.ListTreeNodesRequest, - stream rpc.RepositoryService_ListTreeNodesServer) error { +func (s RepositoryService) ListTreeNodes( + request *rpc.ListTreeNodesRequest, + stream rpc.RepositoryService_ListTreeNodesServer, +) error { ctx := stream.Context() base := request.GetBase() if base == nil { @@ -26,7 +29,7 @@ func (s RepositoryService) ListTreeNodes(request *rpc.ListTreeNodesRequest, repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid()) gitNodes, err := s.adapter.ListTreeNodes(ctx, repoPath, - request.GetGitRef(), request.GetPath(), request.GetRecursive(), request.GetIncludeLatestCommit()) + request.GetGitRef(), request.GetPath()) if err != nil { return processGitErrorf(err, "failed to list tree nodes") } @@ -34,14 +37,6 @@ func (s RepositoryService) ListTreeNodes(request *rpc.ListTreeNodesRequest, log.Ctx(ctx).Trace().Msgf("git adapter returned %d nodes", len(gitNodes)) for _, gitNode := range gitNodes { - var commit *rpc.Commit - if request.GetIncludeLatestCommit() { - commit, err = mapGitCommit(gitNode.Commit) - if err != nil { - return status.Errorf(codes.Internal, "failed to map git commit: %v", err) - } - } - err = stream.Send(&rpc.ListTreeNodesResponse{ Node: &rpc.TreeNode{ Type: mapGitNodeType(gitNode.NodeType), @@ -50,7 +45,6 @@ func (s RepositoryService) ListTreeNodes(request *rpc.ListTreeNodesRequest, Name: gitNode.Name, Path: gitNode.Path, }, - Commit: commit, }) if err != nil { return status.Errorf(codes.Internal, "failed to send node: %v", err) @@ -61,15 +55,16 @@ func (s RepositoryService) ListTreeNodes(request *rpc.ListTreeNodesRequest, } func (s RepositoryService) GetTreeNode(ctx context.Context, - request *rpc.GetTreeNodeRequest) (*rpc.GetTreeNodeResponse, error) { + request *rpc.GetTreeNodeRequest, +) (*rpc.GetTreeNodeResponse, error) { base := request.GetBase() if base == nil { return nil, types.ErrBaseCannotBeEmpty } repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid()) - // TODO: do we need to validate request for nil? - gitNode, err := s.adapter.GetTreeNode(ctx, repoPath, request.GetGitRef(), request.GetPath()) + + gitNode, err := s.adapter.GetTreeNode(ctx, repoPath, request.GitRef, request.Path) if err != nil { return nil, processGitErrorf(err, "no such path '%s' in '%s'", request.Path, request.GetGitRef()) } @@ -84,15 +79,61 @@ func (s RepositoryService) GetTreeNode(ctx context.Context, }, } - // TODO: improve performance, could be done in lower layer? if request.GetIncludeLatestCommit() { - var commit *rpc.Commit - commit, err = s.getLatestCommit(ctx, repoPath, request.GetGitRef(), request.GetPath()) + pathDetails, err := s.adapter.PathsDetails(ctx, repoPath, request.GitRef, []string{request.Path}) if err != nil { return nil, err } - res.Commit = commit + + if len(pathDetails) != 1 { + return nil, fmt.Errorf("failed to get details for the path %s", request.Path) + } + + if pathDetails[0].LastCommit != nil { + res.Commit, err = mapGitCommit(pathDetails[0].LastCommit) + if err != nil { + return nil, err + } + } } return res, nil } + +func (s RepositoryService) PathsDetails(ctx context.Context, + request *rpc.PathsDetailsRequest, +) (*rpc.PathsDetailsResponse, error) { + base := request.GetBase() + if base == nil { + return nil, types.ErrBaseCannotBeEmpty + } + + repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid()) + + pathsDetails, err := s.adapter.PathsDetails(ctx, repoPath, request.GetGitRef(), request.GetPaths()) + if err != nil { + return nil, processGitErrorf(err, "failed to get path details in '%s'", request.GetGitRef()) + } + + details := make([]*rpc.PathDetails, len(pathsDetails)) + for i, pathDetails := range pathsDetails { + var lastCommit *rpc.Commit + + if pathDetails.LastCommit != nil { + lastCommit, err = mapGitCommit(pathDetails.LastCommit) + if err != nil { + return nil, fmt.Errorf("failed to map commit: %w", err) + } + } + + details[i] = &rpc.PathDetails{ + Path: pathDetails.Path, + LastCommit: lastCommit, + Size: pathDetails.Size, + } + } + + return &rpc.PathsDetailsResponse{ + PathDetails: details, + }, nil +} diff --git a/gitrpc/internal/types/errors.go b/gitrpc/internal/types/errors.go index fb8b6cd29..27161c883 100644 --- a/gitrpc/internal/types/errors.go +++ b/gitrpc/internal/types/errors.go @@ -15,6 +15,7 @@ var ( ErrAlreadyExists = errors.New("already exists") ErrInvalidArgument = errors.New("invalid argument") ErrNotFound = errors.New("not found") + ErrPathNotFound = errors.New("path not found") ErrInvalidPath = errors.New("path is invalid") ErrUndefinedAction = errors.New("undefined action") ErrActionNotAllowedOnEmptyRepo = errors.New("action not allowed on empty repository") diff --git a/gitrpc/internal/types/types.go b/gitrpc/internal/types/types.go index 0d9cff775..5bb7964c1 100644 --- a/gitrpc/internal/types/types.go +++ b/gitrpc/internal/types/types.go @@ -320,3 +320,9 @@ type TempRepository struct { BaseSHA string HeadSHA string } + +type PathDetails struct { + Path string + LastCommit *Commit + Size int64 +} diff --git a/gitrpc/operations.go b/gitrpc/operations.go index c680c7cb8..5291b10f6 100644 --- a/gitrpc/operations.go +++ b/gitrpc/operations.go @@ -8,7 +8,6 @@ import ( "bytes" "context" "errors" - "fmt" "io" "time" @@ -66,7 +65,7 @@ type CommitFilesResponse struct { func (c *Client) CommitFiles(ctx context.Context, params *CommitFilesParams) (CommitFilesResponse, error) { stream, err := c.commitFilesService.CommitFiles(ctx) if err != nil { - return CommitFilesResponse{}, err + return CommitFilesResponse{}, processRPCErrorf(err, "failed to open file stream") } if err = stream.Send(&rpc.CommitFilesRequest{ @@ -84,7 +83,7 @@ func (c *Client) CommitFiles(ctx context.Context, params *CommitFilesParams) (Co }, }, }); err != nil { - return CommitFilesResponse{}, err + return CommitFilesResponse{}, processRPCErrorf(err, "failed to send file headers") } for _, action := range params.Actions { @@ -103,7 +102,7 @@ func (c *Client) CommitFiles(ctx context.Context, params *CommitFilesParams) (Co }, }, }); err != nil { - return CommitFilesResponse{}, err + return CommitFilesResponse{}, processRPCErrorf(err, "failed to send file action to the stream") } // send file content @@ -115,7 +114,7 @@ func (c *Client) CommitFiles(ctx context.Context, params *CommitFilesParams) (Co break } if err != nil { - return CommitFilesResponse{}, fmt.Errorf("cannot read buffer: %w", err) + return CommitFilesResponse{}, processRPCErrorf(err, "cannot read buffer") } if err = stream.Send(&rpc.CommitFilesRequest{ @@ -127,14 +126,14 @@ func (c *Client) CommitFiles(ctx context.Context, params *CommitFilesParams) (Co }, }, }); err != nil { - return CommitFilesResponse{}, err + return CommitFilesResponse{}, processRPCErrorf(err, "failed to send file to the stream") } } } recv, err := stream.CloseAndRecv() if err != nil { - return CommitFilesResponse{}, err + return CommitFilesResponse{}, processRPCErrorf(err, "failed to close the stream") } return CommitFilesResponse{ diff --git a/gitrpc/proto/repo.proto b/gitrpc/proto/repo.proto index c0c819c4c..d24861a38 100644 --- a/gitrpc/proto/repo.proto +++ b/gitrpc/proto/repo.proto @@ -10,6 +10,7 @@ service RepositoryService { rpc CreateRepository(stream CreateRepositoryRequest) returns (CreateRepositoryResponse); rpc GetTreeNode(GetTreeNodeRequest) returns (GetTreeNodeResponse); rpc ListTreeNodes(ListTreeNodesRequest) returns (stream ListTreeNodesResponse); + rpc PathsDetails(PathsDetailsRequest) returns (PathsDetailsResponse); rpc GetSubmodule(GetSubmoduleRequest) returns (GetSubmoduleResponse); rpc GetBlob(GetBlobRequest) returns (stream GetBlobResponse); rpc ListCommits(ListCommitsRequest) returns (stream ListCommitsResponse); @@ -55,13 +56,10 @@ message ListTreeNodesRequest { ReadRequest base = 1; string git_ref = 2; string path = 3; - bool include_latest_commit = 4; - bool recursive = 5; } message ListTreeNodesResponse { TreeNode node = 1; - Commit commit = 2; } message TreeNode { @@ -86,6 +84,22 @@ enum TreeNodeMode { TreeNodeModeCommit = 4; } +message PathsDetailsRequest { + ReadRequest base = 1; + string git_ref = 2; + repeated string paths = 3; +} + +message PathsDetailsResponse { + repeated PathDetails path_details = 1; +} + +message PathDetails { + string path = 1; + Commit last_commit = 2; + int64 size = 3; +} + message GetCommitRequest { ReadRequest base = 1; string sha = 2; diff --git a/gitrpc/rpc/repo.pb.go b/gitrpc/rpc/repo.pb.go index 5c9f42d3e..ba0029a59 100644 --- a/gitrpc/rpc/repo.pb.go +++ b/gitrpc/rpc/repo.pb.go @@ -547,11 +547,9 @@ type ListTreeNodesRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Base *ReadRequest `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` - GitRef string `protobuf:"bytes,2,opt,name=git_ref,json=gitRef,proto3" json:"git_ref,omitempty"` - Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` - IncludeLatestCommit bool `protobuf:"varint,4,opt,name=include_latest_commit,json=includeLatestCommit,proto3" json:"include_latest_commit,omitempty"` - Recursive bool `protobuf:"varint,5,opt,name=recursive,proto3" json:"recursive,omitempty"` + Base *ReadRequest `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + GitRef string `protobuf:"bytes,2,opt,name=git_ref,json=gitRef,proto3" json:"git_ref,omitempty"` + Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` } func (x *ListTreeNodesRequest) Reset() { @@ -607,27 +605,12 @@ func (x *ListTreeNodesRequest) GetPath() string { return "" } -func (x *ListTreeNodesRequest) GetIncludeLatestCommit() bool { - if x != nil { - return x.IncludeLatestCommit - } - return false -} - -func (x *ListTreeNodesRequest) GetRecursive() bool { - if x != nil { - return x.Recursive - } - return false -} - type ListTreeNodesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Node *TreeNode `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` - Commit *Commit `protobuf:"bytes,2,opt,name=commit,proto3" json:"commit,omitempty"` + Node *TreeNode `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` } func (x *ListTreeNodesResponse) Reset() { @@ -669,13 +652,6 @@ func (x *ListTreeNodesResponse) GetNode() *TreeNode { return nil } -func (x *ListTreeNodesResponse) GetCommit() *Commit { - if x != nil { - return x.Commit - } - return nil -} - type TreeNode struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -755,6 +731,179 @@ func (x *TreeNode) GetPath() string { return "" } +type PathsDetailsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *ReadRequest `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + GitRef string `protobuf:"bytes,2,opt,name=git_ref,json=gitRef,proto3" json:"git_ref,omitempty"` + Paths []string `protobuf:"bytes,3,rep,name=paths,proto3" json:"paths,omitempty"` +} + +func (x *PathsDetailsRequest) Reset() { + *x = PathsDetailsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_repo_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PathsDetailsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PathsDetailsRequest) ProtoMessage() {} + +func (x *PathsDetailsRequest) ProtoReflect() protoreflect.Message { + mi := &file_repo_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PathsDetailsRequest.ProtoReflect.Descriptor instead. +func (*PathsDetailsRequest) Descriptor() ([]byte, []int) { + return file_repo_proto_rawDescGZIP(), []int{8} +} + +func (x *PathsDetailsRequest) GetBase() *ReadRequest { + if x != nil { + return x.Base + } + return nil +} + +func (x *PathsDetailsRequest) GetGitRef() string { + if x != nil { + return x.GitRef + } + return "" +} + +func (x *PathsDetailsRequest) GetPaths() []string { + if x != nil { + return x.Paths + } + return nil +} + +type PathsDetailsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PathDetails []*PathDetails `protobuf:"bytes,1,rep,name=path_details,json=pathDetails,proto3" json:"path_details,omitempty"` +} + +func (x *PathsDetailsResponse) Reset() { + *x = PathsDetailsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_repo_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PathsDetailsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PathsDetailsResponse) ProtoMessage() {} + +func (x *PathsDetailsResponse) ProtoReflect() protoreflect.Message { + mi := &file_repo_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PathsDetailsResponse.ProtoReflect.Descriptor instead. +func (*PathsDetailsResponse) Descriptor() ([]byte, []int) { + return file_repo_proto_rawDescGZIP(), []int{9} +} + +func (x *PathsDetailsResponse) GetPathDetails() []*PathDetails { + if x != nil { + return x.PathDetails + } + return nil +} + +type PathDetails struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + LastCommit *Commit `protobuf:"bytes,2,opt,name=last_commit,json=lastCommit,proto3" json:"last_commit,omitempty"` + Size int64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` +} + +func (x *PathDetails) Reset() { + *x = PathDetails{} + if protoimpl.UnsafeEnabled { + mi := &file_repo_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PathDetails) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PathDetails) ProtoMessage() {} + +func (x *PathDetails) ProtoReflect() protoreflect.Message { + mi := &file_repo_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PathDetails.ProtoReflect.Descriptor instead. +func (*PathDetails) Descriptor() ([]byte, []int) { + return file_repo_proto_rawDescGZIP(), []int{10} +} + +func (x *PathDetails) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *PathDetails) GetLastCommit() *Commit { + if x != nil { + return x.LastCommit + } + return nil +} + +func (x *PathDetails) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + type GetCommitRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -767,7 +916,7 @@ type GetCommitRequest struct { func (x *GetCommitRequest) Reset() { *x = GetCommitRequest{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[8] + mi := &file_repo_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -780,7 +929,7 @@ func (x *GetCommitRequest) String() string { func (*GetCommitRequest) ProtoMessage() {} func (x *GetCommitRequest) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[8] + mi := &file_repo_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -793,7 +942,7 @@ func (x *GetCommitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCommitRequest.ProtoReflect.Descriptor instead. func (*GetCommitRequest) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{8} + return file_repo_proto_rawDescGZIP(), []int{11} } func (x *GetCommitRequest) GetBase() *ReadRequest { @@ -821,7 +970,7 @@ type GetCommitResponse struct { func (x *GetCommitResponse) Reset() { *x = GetCommitResponse{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[9] + mi := &file_repo_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -834,7 +983,7 @@ func (x *GetCommitResponse) String() string { func (*GetCommitResponse) ProtoMessage() {} func (x *GetCommitResponse) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[9] + mi := &file_repo_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -847,7 +996,7 @@ func (x *GetCommitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCommitResponse.ProtoReflect.Descriptor instead. func (*GetCommitResponse) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{9} + return file_repo_proto_rawDescGZIP(), []int{12} } func (x *GetCommitResponse) GetCommit() *Commit { @@ -876,7 +1025,7 @@ type ListCommitsRequest struct { func (x *ListCommitsRequest) Reset() { *x = ListCommitsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[10] + mi := &file_repo_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -889,7 +1038,7 @@ func (x *ListCommitsRequest) String() string { func (*ListCommitsRequest) ProtoMessage() {} func (x *ListCommitsRequest) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[10] + mi := &file_repo_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -902,7 +1051,7 @@ func (x *ListCommitsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCommitsRequest.ProtoReflect.Descriptor instead. func (*ListCommitsRequest) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{10} + return file_repo_proto_rawDescGZIP(), []int{13} } func (x *ListCommitsRequest) GetBase() *ReadRequest { @@ -980,7 +1129,7 @@ type ListCommitsResponse struct { func (x *ListCommitsResponse) Reset() { *x = ListCommitsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[11] + mi := &file_repo_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -993,7 +1142,7 @@ func (x *ListCommitsResponse) String() string { func (*ListCommitsResponse) ProtoMessage() {} func (x *ListCommitsResponse) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[11] + mi := &file_repo_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1006,7 +1155,7 @@ func (x *ListCommitsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCommitsResponse.ProtoReflect.Descriptor instead. func (*ListCommitsResponse) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{11} + return file_repo_proto_rawDescGZIP(), []int{14} } func (x *ListCommitsResponse) GetCommit() *Commit { @@ -1037,7 +1186,7 @@ type RenameDetails struct { func (x *RenameDetails) Reset() { *x = RenameDetails{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[12] + mi := &file_repo_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1050,7 +1199,7 @@ func (x *RenameDetails) String() string { func (*RenameDetails) ProtoMessage() {} func (x *RenameDetails) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[12] + mi := &file_repo_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1063,7 +1212,7 @@ func (x *RenameDetails) ProtoReflect() protoreflect.Message { // Deprecated: Use RenameDetails.ProtoReflect.Descriptor instead. func (*RenameDetails) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{12} + return file_repo_proto_rawDescGZIP(), []int{15} } func (x *RenameDetails) GetOldPath() string { @@ -1107,7 +1256,7 @@ type GetBlobRequest struct { func (x *GetBlobRequest) Reset() { *x = GetBlobRequest{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[13] + mi := &file_repo_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1120,7 +1269,7 @@ func (x *GetBlobRequest) String() string { func (*GetBlobRequest) ProtoMessage() {} func (x *GetBlobRequest) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[13] + mi := &file_repo_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1133,7 +1282,7 @@ func (x *GetBlobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBlobRequest.ProtoReflect.Descriptor instead. func (*GetBlobRequest) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{13} + return file_repo_proto_rawDescGZIP(), []int{16} } func (x *GetBlobRequest) GetBase() *ReadRequest { @@ -1172,7 +1321,7 @@ type GetBlobResponse struct { func (x *GetBlobResponse) Reset() { *x = GetBlobResponse{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[14] + mi := &file_repo_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1185,7 +1334,7 @@ func (x *GetBlobResponse) String() string { func (*GetBlobResponse) ProtoMessage() {} func (x *GetBlobResponse) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[14] + mi := &file_repo_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1198,7 +1347,7 @@ func (x *GetBlobResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBlobResponse.ProtoReflect.Descriptor instead. func (*GetBlobResponse) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{14} + return file_repo_proto_rawDescGZIP(), []int{17} } func (m *GetBlobResponse) GetData() isGetBlobResponse_Data { @@ -1251,7 +1400,7 @@ type GetBlobResponseHeader struct { func (x *GetBlobResponseHeader) Reset() { *x = GetBlobResponseHeader{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[15] + mi := &file_repo_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1264,7 +1413,7 @@ func (x *GetBlobResponseHeader) String() string { func (*GetBlobResponseHeader) ProtoMessage() {} func (x *GetBlobResponseHeader) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[15] + mi := &file_repo_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1277,7 +1426,7 @@ func (x *GetBlobResponseHeader) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBlobResponseHeader.ProtoReflect.Descriptor instead. func (*GetBlobResponseHeader) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{15} + return file_repo_proto_rawDescGZIP(), []int{18} } func (x *GetBlobResponseHeader) GetSha() string { @@ -1314,7 +1463,7 @@ type GetSubmoduleRequest struct { func (x *GetSubmoduleRequest) Reset() { *x = GetSubmoduleRequest{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[16] + mi := &file_repo_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1327,7 +1476,7 @@ func (x *GetSubmoduleRequest) String() string { func (*GetSubmoduleRequest) ProtoMessage() {} func (x *GetSubmoduleRequest) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[16] + mi := &file_repo_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1340,7 +1489,7 @@ func (x *GetSubmoduleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSubmoduleRequest.ProtoReflect.Descriptor instead. func (*GetSubmoduleRequest) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{16} + return file_repo_proto_rawDescGZIP(), []int{19} } func (x *GetSubmoduleRequest) GetBase() *ReadRequest { @@ -1375,7 +1524,7 @@ type GetSubmoduleResponse struct { func (x *GetSubmoduleResponse) Reset() { *x = GetSubmoduleResponse{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[17] + mi := &file_repo_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1388,7 +1537,7 @@ func (x *GetSubmoduleResponse) String() string { func (*GetSubmoduleResponse) ProtoMessage() {} func (x *GetSubmoduleResponse) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[17] + mi := &file_repo_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1401,7 +1550,7 @@ func (x *GetSubmoduleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSubmoduleResponse.ProtoReflect.Descriptor instead. func (*GetSubmoduleResponse) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{17} + return file_repo_proto_rawDescGZIP(), []int{20} } func (x *GetSubmoduleResponse) GetSubmodule() *Submodule { @@ -1423,7 +1572,7 @@ type Submodule struct { func (x *Submodule) Reset() { *x = Submodule{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[18] + mi := &file_repo_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1436,7 +1585,7 @@ func (x *Submodule) String() string { func (*Submodule) ProtoMessage() {} func (x *Submodule) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[18] + mi := &file_repo_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1449,7 +1598,7 @@ func (x *Submodule) ProtoReflect() protoreflect.Message { // Deprecated: Use Submodule.ProtoReflect.Descriptor instead. func (*Submodule) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{18} + return file_repo_proto_rawDescGZIP(), []int{21} } func (x *Submodule) GetName() string { @@ -1479,7 +1628,7 @@ type GetCommitDivergencesRequest struct { func (x *GetCommitDivergencesRequest) Reset() { *x = GetCommitDivergencesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[19] + mi := &file_repo_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1492,7 +1641,7 @@ func (x *GetCommitDivergencesRequest) String() string { func (*GetCommitDivergencesRequest) ProtoMessage() {} func (x *GetCommitDivergencesRequest) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[19] + mi := &file_repo_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1505,7 +1654,7 @@ func (x *GetCommitDivergencesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCommitDivergencesRequest.ProtoReflect.Descriptor instead. func (*GetCommitDivergencesRequest) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{19} + return file_repo_proto_rawDescGZIP(), []int{22} } func (x *GetCommitDivergencesRequest) GetBase() *ReadRequest { @@ -1541,7 +1690,7 @@ type CommitDivergenceRequest struct { func (x *CommitDivergenceRequest) Reset() { *x = CommitDivergenceRequest{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[20] + mi := &file_repo_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1554,7 +1703,7 @@ func (x *CommitDivergenceRequest) String() string { func (*CommitDivergenceRequest) ProtoMessage() {} func (x *CommitDivergenceRequest) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[20] + mi := &file_repo_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1567,7 +1716,7 @@ func (x *CommitDivergenceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CommitDivergenceRequest.ProtoReflect.Descriptor instead. func (*CommitDivergenceRequest) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{20} + return file_repo_proto_rawDescGZIP(), []int{23} } func (x *CommitDivergenceRequest) GetFrom() string { @@ -1595,7 +1744,7 @@ type GetCommitDivergencesResponse struct { func (x *GetCommitDivergencesResponse) Reset() { *x = GetCommitDivergencesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[21] + mi := &file_repo_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1608,7 +1757,7 @@ func (x *GetCommitDivergencesResponse) String() string { func (*GetCommitDivergencesResponse) ProtoMessage() {} func (x *GetCommitDivergencesResponse) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[21] + mi := &file_repo_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1621,7 +1770,7 @@ func (x *GetCommitDivergencesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCommitDivergencesResponse.ProtoReflect.Descriptor instead. func (*GetCommitDivergencesResponse) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{21} + return file_repo_proto_rawDescGZIP(), []int{24} } func (x *GetCommitDivergencesResponse) GetDivergences() []*CommitDivergence { @@ -1643,7 +1792,7 @@ type CommitDivergence struct { func (x *CommitDivergence) Reset() { *x = CommitDivergence{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[22] + mi := &file_repo_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1656,7 +1805,7 @@ func (x *CommitDivergence) String() string { func (*CommitDivergence) ProtoMessage() {} func (x *CommitDivergence) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[22] + mi := &file_repo_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1669,7 +1818,7 @@ func (x *CommitDivergence) ProtoReflect() protoreflect.Message { // Deprecated: Use CommitDivergence.ProtoReflect.Descriptor instead. func (*CommitDivergence) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{22} + return file_repo_proto_rawDescGZIP(), []int{25} } func (x *CommitDivergence) GetAhead() int32 { @@ -1697,7 +1846,7 @@ type DeleteRepositoryRequest struct { func (x *DeleteRepositoryRequest) Reset() { *x = DeleteRepositoryRequest{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[23] + mi := &file_repo_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1710,7 +1859,7 @@ func (x *DeleteRepositoryRequest) String() string { func (*DeleteRepositoryRequest) ProtoMessage() {} func (x *DeleteRepositoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[23] + mi := &file_repo_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1723,7 +1872,7 @@ func (x *DeleteRepositoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteRepositoryRequest.ProtoReflect.Descriptor instead. func (*DeleteRepositoryRequest) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{23} + return file_repo_proto_rawDescGZIP(), []int{26} } func (x *DeleteRepositoryRequest) GetBase() *WriteRequest { @@ -1742,7 +1891,7 @@ type DeleteRepositoryResponse struct { func (x *DeleteRepositoryResponse) Reset() { *x = DeleteRepositoryResponse{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[24] + mi := &file_repo_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1755,7 +1904,7 @@ func (x *DeleteRepositoryResponse) String() string { func (*DeleteRepositoryResponse) ProtoMessage() {} func (x *DeleteRepositoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[24] + mi := &file_repo_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1768,7 +1917,7 @@ func (x *DeleteRepositoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteRepositoryResponse.ProtoReflect.Descriptor instead. func (*DeleteRepositoryResponse) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{24} + return file_repo_proto_rawDescGZIP(), []int{27} } type SyncRepositoryRequest struct { @@ -1784,7 +1933,7 @@ type SyncRepositoryRequest struct { func (x *SyncRepositoryRequest) Reset() { *x = SyncRepositoryRequest{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[25] + mi := &file_repo_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1797,7 +1946,7 @@ func (x *SyncRepositoryRequest) String() string { func (*SyncRepositoryRequest) ProtoMessage() {} func (x *SyncRepositoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[25] + mi := &file_repo_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1810,7 +1959,7 @@ func (x *SyncRepositoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncRepositoryRequest.ProtoReflect.Descriptor instead. func (*SyncRepositoryRequest) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{25} + return file_repo_proto_rawDescGZIP(), []int{28} } func (x *SyncRepositoryRequest) GetBase() *WriteRequest { @@ -1843,7 +1992,7 @@ type SyncRepositoryResponse struct { func (x *SyncRepositoryResponse) Reset() { *x = SyncRepositoryResponse{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[26] + mi := &file_repo_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1856,7 +2005,7 @@ func (x *SyncRepositoryResponse) String() string { func (*SyncRepositoryResponse) ProtoMessage() {} func (x *SyncRepositoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[26] + mi := &file_repo_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1869,7 +2018,7 @@ func (x *SyncRepositoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncRepositoryResponse.ProtoReflect.Descriptor instead. func (*SyncRepositoryResponse) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{26} + return file_repo_proto_rawDescGZIP(), []int{29} } type HashRepositoryRequest struct { @@ -1885,7 +2034,7 @@ type HashRepositoryRequest struct { func (x *HashRepositoryRequest) Reset() { *x = HashRepositoryRequest{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[27] + mi := &file_repo_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1898,7 +2047,7 @@ func (x *HashRepositoryRequest) String() string { func (*HashRepositoryRequest) ProtoMessage() {} func (x *HashRepositoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[27] + mi := &file_repo_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1911,7 +2060,7 @@ func (x *HashRepositoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HashRepositoryRequest.ProtoReflect.Descriptor instead. func (*HashRepositoryRequest) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{27} + return file_repo_proto_rawDescGZIP(), []int{30} } func (x *HashRepositoryRequest) GetBase() *ReadRequest { @@ -1946,7 +2095,7 @@ type HashRepositoryResponse struct { func (x *HashRepositoryResponse) Reset() { *x = HashRepositoryResponse{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[28] + mi := &file_repo_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1959,7 +2108,7 @@ func (x *HashRepositoryResponse) String() string { func (*HashRepositoryResponse) ProtoMessage() {} func (x *HashRepositoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[28] + mi := &file_repo_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1972,7 +2121,7 @@ func (x *HashRepositoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HashRepositoryResponse.ProtoReflect.Descriptor instead. func (*HashRepositoryResponse) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{28} + return file_repo_proto_rawDescGZIP(), []int{31} } func (x *HashRepositoryResponse) GetHash() []byte { @@ -1995,7 +2144,7 @@ type MergeBaseRequest struct { func (x *MergeBaseRequest) Reset() { *x = MergeBaseRequest{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[29] + mi := &file_repo_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2008,7 +2157,7 @@ func (x *MergeBaseRequest) String() string { func (*MergeBaseRequest) ProtoMessage() {} func (x *MergeBaseRequest) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[29] + mi := &file_repo_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2021,7 +2170,7 @@ func (x *MergeBaseRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MergeBaseRequest.ProtoReflect.Descriptor instead. func (*MergeBaseRequest) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{29} + return file_repo_proto_rawDescGZIP(), []int{32} } func (x *MergeBaseRequest) GetBase() *ReadRequest { @@ -2056,7 +2205,7 @@ type MergeBaseResponse struct { func (x *MergeBaseResponse) Reset() { *x = MergeBaseResponse{} if protoimpl.UnsafeEnabled { - mi := &file_repo_proto_msgTypes[30] + mi := &file_repo_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2069,7 +2218,7 @@ func (x *MergeBaseResponse) String() string { func (*MergeBaseResponse) ProtoMessage() {} func (x *MergeBaseResponse) ProtoReflect() protoreflect.Message { - mi := &file_repo_proto_msgTypes[30] + mi := &file_repo_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2082,7 +2231,7 @@ func (x *MergeBaseResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MergeBaseResponse.ProtoReflect.Descriptor instead. func (*MergeBaseResponse) Descriptor() ([]byte, []int) { - return file_repo_proto_rawDescGZIP(), []int{30} + return file_repo_proto_rawDescGZIP(), []int{33} } func (x *MergeBaseResponse) GetMergeBaseSha() string { @@ -2139,249 +2288,264 @@ var file_repo_proto_rawDesc = []byte{ 0x0b, 0x32, 0x0d, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x22, 0xbb, 0x01, 0x0a, 0x14, - 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, - 0x74, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x74, - 0x52, 0x65, 0x66, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x32, 0x0a, 0x15, 0x69, 0x6e, 0x63, 0x6c, 0x75, - 0x64, 0x65, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x4c, - 0x61, 0x74, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, - 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, - 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x22, 0x5f, 0x0a, 0x15, 0x4c, 0x69, 0x73, - 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x21, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0d, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, - 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x22, 0x92, 0x01, 0x0a, 0x08, 0x54, - 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x65, 0x65, - 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x25, - 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x72, - 0x70, 0x63, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x52, - 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x68, 0x61, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, - 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, - 0x4a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x6d, 0x69, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x22, 0x69, 0x0a, 0x14, 0x4c, + 0x69, 0x73, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x68, 0x61, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x22, 0x38, 0x0a, 0x11, 0x47, - 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x06, 0x63, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x22, 0xf1, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, - 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, - 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, - 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x74, 0x52, 0x65, 0x66, 0x12, 0x14, 0x0a, 0x05, 0x61, - 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x66, 0x74, 0x65, - 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, - 0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, - 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x63, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x22, 0x75, 0x0a, 0x13, 0x4c, 0x69, 0x73, - 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x06, 0x63, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x39, 0x0a, 0x0e, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x5f, - 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, - 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x52, 0x0d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, - 0x22, 0x9b, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6c, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x6c, 0x64, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, - 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x6e, 0x65, 0x77, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x68, 0x61, 0x42, 0x65, - 0x66, 0x6f, 0x72, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x73, - 0x68, 0x61, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x68, 0x61, 0x41, 0x66, 0x74, 0x65, 0x72, 0x22, 0x67, - 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, - 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x68, 0x61, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, 0x7a, 0x65, - 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x69, - 0x7a, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x6b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, 0x6c, - 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x68, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x12, 0x1a, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x42, 0x06, 0x0a, 0x04, - 0x64, 0x61, 0x74, 0x61, 0x22, 0x60, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, - 0x03, 0x73, 0x68, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x12, - 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, - 0x69, 0x7a, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x6e, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x68, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, - 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, - 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, - 0x61, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x74, 0x52, 0x65, 0x66, 0x12, 0x12, 0x0a, 0x04, - 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, - 0x22, 0x44, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x6d, - 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x09, 0x73, 0x75, 0x62, - 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x31, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x6d, 0x6f, 0x64, - 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x9a, 0x01, 0x0a, 0x1b, 0x47, 0x65, - 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, + 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x74, + 0x5f, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x74, 0x52, + 0x65, 0x66, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x3a, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, + 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x21, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, + 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, + 0x64, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x08, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, + 0x25, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, + 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x4e, + 0x6f, 0x64, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, + 0x03, 0x73, 0x68, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x6a, 0x0a, 0x13, 0x50, 0x61, 0x74, 0x68, 0x73, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, + 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, + 0x62, 0x61, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x74, 0x52, 0x65, 0x66, 0x12, 0x14, 0x0a, + 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, + 0x74, 0x68, 0x73, 0x22, 0x4b, 0x0a, 0x14, 0x50, 0x61, 0x74, 0x68, 0x73, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0c, 0x70, + 0x61, 0x74, 0x68, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x52, 0x0b, 0x70, 0x61, 0x74, 0x68, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x22, 0x63, 0x0a, 0x0b, 0x50, 0x61, 0x74, 0x68, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, + 0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x4a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x08, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x69, 0x76, 0x65, 0x72, - 0x67, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x08, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x22, 0x3d, 0x0a, 0x17, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x57, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x64, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, - 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, - 0x65, 0x52, 0x0b, 0x64, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x40, - 0x0a, 0x10, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, - 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x68, 0x65, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x05, 0x61, 0x68, 0x65, 0x61, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x65, 0x68, 0x69, - 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x62, 0x65, 0x68, 0x69, 0x6e, 0x64, - 0x22, 0x40, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x62, - 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, - 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, - 0x73, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, - 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x87, - 0x01, 0x0a, 0x15, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x72, 0x69, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x14, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x5f, 0x69, 0x66, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x66, 0x4e, - 0x6f, 0x74, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x53, 0x79, 0x6e, 0x63, - 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0xae, 0x01, 0x0a, 0x15, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, - 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, - 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, - 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x09, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x61, 0x73, 0x68, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x68, 0x61, 0x73, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x43, - 0x0a, 0x10, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x48, - 0x61, 0x73, 0x68, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, - 0x70, 0x65, 0x52, 0x0f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, - 0x79, 0x70, 0x65, 0x22, 0x2c, 0x0a, 0x16, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, - 0x68, 0x22, 0x60, 0x0a, 0x10, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, - 0x65, 0x66, 0x31, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x65, 0x66, 0x31, 0x12, - 0x12, 0x0a, 0x04, 0x72, 0x65, 0x66, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, - 0x65, 0x66, 0x32, 0x22, 0x39, 0x0a, 0x11, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x65, 0x72, 0x67, - 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x68, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, 0x53, 0x68, 0x61, 0x2a, 0x52, - 0x0a, 0x0c, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, - 0x0a, 0x10, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x54, 0x72, - 0x65, 0x65, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x54, 0x72, - 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x10, 0x02, 0x2a, 0x81, 0x01, 0x0a, 0x0c, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x4d, - 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x4d, - 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x72, 0x65, - 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x53, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, - 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x6f, - 0x64, 0x65, 0x45, 0x78, 0x65, 0x63, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x72, 0x65, 0x65, - 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x54, 0x72, 0x65, 0x65, 0x10, 0x03, 0x12, 0x16, - 0x0a, 0x12, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x10, 0x04, 0x2a, 0x1e, 0x0a, 0x08, 0x48, 0x61, 0x73, 0x68, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x48, 0x61, 0x73, 0x68, 0x54, 0x79, 0x70, 0x65, 0x53, 0x48, - 0x41, 0x32, 0x35, 0x36, 0x10, 0x00, 0x2a, 0x31, 0x0a, 0x13, 0x48, 0x61, 0x73, 0x68, 0x41, 0x67, - 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, - 0x16, 0x48, 0x61, 0x73, 0x68, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x54, 0x79, 0x70, 0x65, 0x58, 0x4f, 0x52, 0x10, 0x00, 0x32, 0xf3, 0x06, 0x0a, 0x11, 0x52, 0x65, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x51, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x28, 0x01, 0x12, 0x40, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, - 0x65, 0x12, 0x17, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, - 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x65, 0x65, - 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, - 0x6f, 0x64, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x43, - 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x18, - 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x13, - 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, - 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0b, 0x4c, - 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x72, 0x70, 0x63, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, - 0x3a, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x15, 0x2e, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x14, 0x47, + 0x10, 0x0a, 0x03, 0x73, 0x68, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, + 0x61, 0x22, 0x38, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x22, 0xf1, 0x01, 0x0a, 0x12, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x74, 0x5f, + 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x74, 0x52, 0x65, + 0x66, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x61, 0x66, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x75, + 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x75, 0x6e, 0x74, 0x69, + 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x22, + 0x75, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x39, 0x0a, 0x0e, 0x72, + 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x9b, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x6e, 0x61, 0x6d, + 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6c, 0x64, 0x5f, + 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x6c, 0x64, 0x50, + 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2a, + 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x5f, 0x62, 0x65, 0x66, + 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x53, 0x68, 0x61, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x53, 0x68, 0x61, 0x41, + 0x66, 0x74, 0x65, 0x72, 0x22, 0x67, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, + 0x73, 0x68, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x73, 0x69, 0x7a, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x6b, 0x0a, + 0x0f, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x34, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, + 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x60, 0x0a, 0x15, 0x47, 0x65, + 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x68, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x73, 0x68, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x68, 0x0a, 0x13, + 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x74, + 0x5f, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x74, 0x52, + 0x65, 0x66, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x44, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, + 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, + 0x0a, 0x09, 0x73, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x52, 0x09, 0x73, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x31, 0x0a, 0x09, + 0x53, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, + 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, + 0x9a, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x69, 0x76, + 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, + 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x52, 0x08, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x22, 0x3d, 0x0a, 0x17, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, + 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x57, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, - 0x63, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x2e, 0x72, - 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x70, 0x63, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x53, 0x79, 0x6e, - 0x63, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1a, 0x2e, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, - 0x6e, 0x63, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1a, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x48, - 0x61, 0x73, 0x68, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, - 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x09, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, - 0x12, 0x15, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x65, - 0x72, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, - 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, - 0x72, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, - 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x64, + 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x15, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x69, 0x76, + 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x64, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, + 0x6e, 0x63, 0x65, 0x73, 0x22, 0x40, 0x0a, 0x10, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x69, + 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x68, 0x65, 0x61, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x61, 0x68, 0x65, 0x61, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x62, 0x65, 0x68, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, + 0x62, 0x65, 0x68, 0x69, 0x6e, 0x64, 0x22, 0x40, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x25, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x11, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x87, 0x01, 0x0a, 0x15, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, + 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, + 0x70, 0x63, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, + 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2f, 0x0a, + 0x14, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x66, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x65, + 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x49, 0x66, 0x4e, 0x6f, 0x74, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x22, 0x18, + 0x0a, 0x16, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xae, 0x01, 0x0a, 0x15, 0x48, 0x61, 0x73, + 0x68, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x09, 0x68, 0x61, 0x73, 0x68, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x72, 0x70, + 0x63, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x68, 0x61, 0x73, 0x68, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x43, 0x0a, 0x10, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x2c, 0x0a, 0x16, 0x48, 0x61, 0x73, + 0x68, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0x60, 0x0a, 0x10, 0x4d, 0x65, 0x72, 0x67, 0x65, + 0x42, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, + 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x62, 0x61, 0x73, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x66, 0x31, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x72, 0x65, 0x66, 0x31, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x66, 0x32, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x65, 0x66, 0x32, 0x22, 0x39, 0x0a, 0x11, 0x4d, 0x65, 0x72, + 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, + 0x0a, 0x0e, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x68, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x61, 0x73, + 0x65, 0x53, 0x68, 0x61, 0x2a, 0x52, 0x0a, 0x0c, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x54, 0x72, 0x65, 0x65, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x72, + 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x10, 0x01, + 0x12, 0x16, 0x0a, 0x12, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x10, 0x02, 0x2a, 0x81, 0x01, 0x0a, 0x0c, 0x54, 0x72, 0x65, + 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x72, 0x65, + 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x10, 0x00, 0x12, + 0x17, 0x0a, 0x13, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x53, + 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x72, 0x65, 0x65, + 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x45, 0x78, 0x65, 0x63, 0x10, 0x02, 0x12, 0x14, + 0x0a, 0x10, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x54, 0x72, + 0x65, 0x65, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, + 0x4d, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x10, 0x04, 0x2a, 0x1e, 0x0a, 0x08, + 0x48, 0x61, 0x73, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x48, 0x61, 0x73, 0x68, + 0x54, 0x79, 0x70, 0x65, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x10, 0x00, 0x2a, 0x31, 0x0a, 0x13, + 0x48, 0x61, 0x73, 0x68, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x48, 0x61, 0x73, 0x68, 0x41, 0x67, 0x67, 0x72, 0x65, + 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x58, 0x4f, 0x52, 0x10, 0x00, 0x32, + 0xb8, 0x07, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x40, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x54, + 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, + 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0d, 0x4c, 0x69, + 0x73, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x72, 0x70, + 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x30, 0x01, 0x12, 0x43, 0x0a, 0x0c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x44, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x12, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0c, 0x47, 0x65, 0x74, + 0x53, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, + 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, + 0x0a, 0x07, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x13, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x09, 0x47, 0x65, + 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x15, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x20, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x69, + 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x21, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x44, 0x69, 0x76, 0x65, 0x72, 0x67, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1a, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, + 0x63, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x4b, 0x0a, 0x0e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x6f, 0x72, 0x79, 0x12, 0x1a, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3a, + 0x0a, 0x09, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, 0x12, 0x15, 0x2e, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x42, 0x61, + 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x72, 0x6e, 0x65, 0x73, 0x73, + 0x2f, 0x67, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2f, + 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2397,7 +2561,7 @@ func file_repo_proto_rawDescGZIP() []byte { } var file_repo_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_repo_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_repo_proto_msgTypes = make([]protoimpl.MessageInfo, 34) var file_repo_proto_goTypes = []interface{}{ (TreeNodeType)(0), // 0: rpc.TreeNodeType (TreeNodeMode)(0), // 1: rpc.TreeNodeMode @@ -2411,96 +2575,103 @@ var file_repo_proto_goTypes = []interface{}{ (*ListTreeNodesRequest)(nil), // 9: rpc.ListTreeNodesRequest (*ListTreeNodesResponse)(nil), // 10: rpc.ListTreeNodesResponse (*TreeNode)(nil), // 11: rpc.TreeNode - (*GetCommitRequest)(nil), // 12: rpc.GetCommitRequest - (*GetCommitResponse)(nil), // 13: rpc.GetCommitResponse - (*ListCommitsRequest)(nil), // 14: rpc.ListCommitsRequest - (*ListCommitsResponse)(nil), // 15: rpc.ListCommitsResponse - (*RenameDetails)(nil), // 16: rpc.RenameDetails - (*GetBlobRequest)(nil), // 17: rpc.GetBlobRequest - (*GetBlobResponse)(nil), // 18: rpc.GetBlobResponse - (*GetBlobResponseHeader)(nil), // 19: rpc.GetBlobResponseHeader - (*GetSubmoduleRequest)(nil), // 20: rpc.GetSubmoduleRequest - (*GetSubmoduleResponse)(nil), // 21: rpc.GetSubmoduleResponse - (*Submodule)(nil), // 22: rpc.Submodule - (*GetCommitDivergencesRequest)(nil), // 23: rpc.GetCommitDivergencesRequest - (*CommitDivergenceRequest)(nil), // 24: rpc.CommitDivergenceRequest - (*GetCommitDivergencesResponse)(nil), // 25: rpc.GetCommitDivergencesResponse - (*CommitDivergence)(nil), // 26: rpc.CommitDivergence - (*DeleteRepositoryRequest)(nil), // 27: rpc.DeleteRepositoryRequest - (*DeleteRepositoryResponse)(nil), // 28: rpc.DeleteRepositoryResponse - (*SyncRepositoryRequest)(nil), // 29: rpc.SyncRepositoryRequest - (*SyncRepositoryResponse)(nil), // 30: rpc.SyncRepositoryResponse - (*HashRepositoryRequest)(nil), // 31: rpc.HashRepositoryRequest - (*HashRepositoryResponse)(nil), // 32: rpc.HashRepositoryResponse - (*MergeBaseRequest)(nil), // 33: rpc.MergeBaseRequest - (*MergeBaseResponse)(nil), // 34: rpc.MergeBaseResponse - (*FileUpload)(nil), // 35: rpc.FileUpload - (*WriteRequest)(nil), // 36: rpc.WriteRequest - (*Identity)(nil), // 37: rpc.Identity - (*ReadRequest)(nil), // 38: rpc.ReadRequest - (*Commit)(nil), // 39: rpc.Commit + (*PathsDetailsRequest)(nil), // 12: rpc.PathsDetailsRequest + (*PathsDetailsResponse)(nil), // 13: rpc.PathsDetailsResponse + (*PathDetails)(nil), // 14: rpc.PathDetails + (*GetCommitRequest)(nil), // 15: rpc.GetCommitRequest + (*GetCommitResponse)(nil), // 16: rpc.GetCommitResponse + (*ListCommitsRequest)(nil), // 17: rpc.ListCommitsRequest + (*ListCommitsResponse)(nil), // 18: rpc.ListCommitsResponse + (*RenameDetails)(nil), // 19: rpc.RenameDetails + (*GetBlobRequest)(nil), // 20: rpc.GetBlobRequest + (*GetBlobResponse)(nil), // 21: rpc.GetBlobResponse + (*GetBlobResponseHeader)(nil), // 22: rpc.GetBlobResponseHeader + (*GetSubmoduleRequest)(nil), // 23: rpc.GetSubmoduleRequest + (*GetSubmoduleResponse)(nil), // 24: rpc.GetSubmoduleResponse + (*Submodule)(nil), // 25: rpc.Submodule + (*GetCommitDivergencesRequest)(nil), // 26: rpc.GetCommitDivergencesRequest + (*CommitDivergenceRequest)(nil), // 27: rpc.CommitDivergenceRequest + (*GetCommitDivergencesResponse)(nil), // 28: rpc.GetCommitDivergencesResponse + (*CommitDivergence)(nil), // 29: rpc.CommitDivergence + (*DeleteRepositoryRequest)(nil), // 30: rpc.DeleteRepositoryRequest + (*DeleteRepositoryResponse)(nil), // 31: rpc.DeleteRepositoryResponse + (*SyncRepositoryRequest)(nil), // 32: rpc.SyncRepositoryRequest + (*SyncRepositoryResponse)(nil), // 33: rpc.SyncRepositoryResponse + (*HashRepositoryRequest)(nil), // 34: rpc.HashRepositoryRequest + (*HashRepositoryResponse)(nil), // 35: rpc.HashRepositoryResponse + (*MergeBaseRequest)(nil), // 36: rpc.MergeBaseRequest + (*MergeBaseResponse)(nil), // 37: rpc.MergeBaseResponse + (*FileUpload)(nil), // 38: rpc.FileUpload + (*WriteRequest)(nil), // 39: rpc.WriteRequest + (*Identity)(nil), // 40: rpc.Identity + (*ReadRequest)(nil), // 41: rpc.ReadRequest + (*Commit)(nil), // 42: rpc.Commit } var file_repo_proto_depIdxs = []int32{ 5, // 0: rpc.CreateRepositoryRequest.header:type_name -> rpc.CreateRepositoryRequestHeader - 35, // 1: rpc.CreateRepositoryRequest.file:type_name -> rpc.FileUpload - 36, // 2: rpc.CreateRepositoryRequestHeader.base:type_name -> rpc.WriteRequest - 37, // 3: rpc.CreateRepositoryRequestHeader.author:type_name -> rpc.Identity - 37, // 4: rpc.CreateRepositoryRequestHeader.committer:type_name -> rpc.Identity - 38, // 5: rpc.GetTreeNodeRequest.base:type_name -> rpc.ReadRequest + 38, // 1: rpc.CreateRepositoryRequest.file:type_name -> rpc.FileUpload + 39, // 2: rpc.CreateRepositoryRequestHeader.base:type_name -> rpc.WriteRequest + 40, // 3: rpc.CreateRepositoryRequestHeader.author:type_name -> rpc.Identity + 40, // 4: rpc.CreateRepositoryRequestHeader.committer:type_name -> rpc.Identity + 41, // 5: rpc.GetTreeNodeRequest.base:type_name -> rpc.ReadRequest 11, // 6: rpc.GetTreeNodeResponse.node:type_name -> rpc.TreeNode - 39, // 7: rpc.GetTreeNodeResponse.commit:type_name -> rpc.Commit - 38, // 8: rpc.ListTreeNodesRequest.base:type_name -> rpc.ReadRequest + 42, // 7: rpc.GetTreeNodeResponse.commit:type_name -> rpc.Commit + 41, // 8: rpc.ListTreeNodesRequest.base:type_name -> rpc.ReadRequest 11, // 9: rpc.ListTreeNodesResponse.node:type_name -> rpc.TreeNode - 39, // 10: rpc.ListTreeNodesResponse.commit:type_name -> rpc.Commit - 0, // 11: rpc.TreeNode.type:type_name -> rpc.TreeNodeType - 1, // 12: rpc.TreeNode.mode:type_name -> rpc.TreeNodeMode - 38, // 13: rpc.GetCommitRequest.base:type_name -> rpc.ReadRequest - 39, // 14: rpc.GetCommitResponse.commit:type_name -> rpc.Commit - 38, // 15: rpc.ListCommitsRequest.base:type_name -> rpc.ReadRequest - 39, // 16: rpc.ListCommitsResponse.commit:type_name -> rpc.Commit - 16, // 17: rpc.ListCommitsResponse.rename_details:type_name -> rpc.RenameDetails - 38, // 18: rpc.GetBlobRequest.base:type_name -> rpc.ReadRequest - 19, // 19: rpc.GetBlobResponse.header:type_name -> rpc.GetBlobResponseHeader - 38, // 20: rpc.GetSubmoduleRequest.base:type_name -> rpc.ReadRequest - 22, // 21: rpc.GetSubmoduleResponse.submodule:type_name -> rpc.Submodule - 38, // 22: rpc.GetCommitDivergencesRequest.base:type_name -> rpc.ReadRequest - 24, // 23: rpc.GetCommitDivergencesRequest.requests:type_name -> rpc.CommitDivergenceRequest - 26, // 24: rpc.GetCommitDivergencesResponse.divergences:type_name -> rpc.CommitDivergence - 36, // 25: rpc.DeleteRepositoryRequest.base:type_name -> rpc.WriteRequest - 36, // 26: rpc.SyncRepositoryRequest.base:type_name -> rpc.WriteRequest - 38, // 27: rpc.HashRepositoryRequest.base:type_name -> rpc.ReadRequest - 2, // 28: rpc.HashRepositoryRequest.hash_type:type_name -> rpc.HashType - 3, // 29: rpc.HashRepositoryRequest.aggregation_type:type_name -> rpc.HashAggregationType - 38, // 30: rpc.MergeBaseRequest.base:type_name -> rpc.ReadRequest - 4, // 31: rpc.RepositoryService.CreateRepository:input_type -> rpc.CreateRepositoryRequest - 7, // 32: rpc.RepositoryService.GetTreeNode:input_type -> rpc.GetTreeNodeRequest - 9, // 33: rpc.RepositoryService.ListTreeNodes:input_type -> rpc.ListTreeNodesRequest - 20, // 34: rpc.RepositoryService.GetSubmodule:input_type -> rpc.GetSubmoduleRequest - 17, // 35: rpc.RepositoryService.GetBlob:input_type -> rpc.GetBlobRequest - 14, // 36: rpc.RepositoryService.ListCommits:input_type -> rpc.ListCommitsRequest - 12, // 37: rpc.RepositoryService.GetCommit:input_type -> rpc.GetCommitRequest - 23, // 38: rpc.RepositoryService.GetCommitDivergences:input_type -> rpc.GetCommitDivergencesRequest - 27, // 39: rpc.RepositoryService.DeleteRepository:input_type -> rpc.DeleteRepositoryRequest - 29, // 40: rpc.RepositoryService.SyncRepository:input_type -> rpc.SyncRepositoryRequest - 31, // 41: rpc.RepositoryService.HashRepository:input_type -> rpc.HashRepositoryRequest - 33, // 42: rpc.RepositoryService.MergeBase:input_type -> rpc.MergeBaseRequest - 6, // 43: rpc.RepositoryService.CreateRepository:output_type -> rpc.CreateRepositoryResponse - 8, // 44: rpc.RepositoryService.GetTreeNode:output_type -> rpc.GetTreeNodeResponse - 10, // 45: rpc.RepositoryService.ListTreeNodes:output_type -> rpc.ListTreeNodesResponse - 21, // 46: rpc.RepositoryService.GetSubmodule:output_type -> rpc.GetSubmoduleResponse - 18, // 47: rpc.RepositoryService.GetBlob:output_type -> rpc.GetBlobResponse - 15, // 48: rpc.RepositoryService.ListCommits:output_type -> rpc.ListCommitsResponse - 13, // 49: rpc.RepositoryService.GetCommit:output_type -> rpc.GetCommitResponse - 25, // 50: rpc.RepositoryService.GetCommitDivergences:output_type -> rpc.GetCommitDivergencesResponse - 28, // 51: rpc.RepositoryService.DeleteRepository:output_type -> rpc.DeleteRepositoryResponse - 30, // 52: rpc.RepositoryService.SyncRepository:output_type -> rpc.SyncRepositoryResponse - 32, // 53: rpc.RepositoryService.HashRepository:output_type -> rpc.HashRepositoryResponse - 34, // 54: rpc.RepositoryService.MergeBase:output_type -> rpc.MergeBaseResponse - 43, // [43:55] is the sub-list for method output_type - 31, // [31:43] is the sub-list for method input_type - 31, // [31:31] is the sub-list for extension type_name - 31, // [31:31] is the sub-list for extension extendee - 0, // [0:31] is the sub-list for field type_name + 0, // 10: rpc.TreeNode.type:type_name -> rpc.TreeNodeType + 1, // 11: rpc.TreeNode.mode:type_name -> rpc.TreeNodeMode + 41, // 12: rpc.PathsDetailsRequest.base:type_name -> rpc.ReadRequest + 14, // 13: rpc.PathsDetailsResponse.path_details:type_name -> rpc.PathDetails + 42, // 14: rpc.PathDetails.last_commit:type_name -> rpc.Commit + 41, // 15: rpc.GetCommitRequest.base:type_name -> rpc.ReadRequest + 42, // 16: rpc.GetCommitResponse.commit:type_name -> rpc.Commit + 41, // 17: rpc.ListCommitsRequest.base:type_name -> rpc.ReadRequest + 42, // 18: rpc.ListCommitsResponse.commit:type_name -> rpc.Commit + 19, // 19: rpc.ListCommitsResponse.rename_details:type_name -> rpc.RenameDetails + 41, // 20: rpc.GetBlobRequest.base:type_name -> rpc.ReadRequest + 22, // 21: rpc.GetBlobResponse.header:type_name -> rpc.GetBlobResponseHeader + 41, // 22: rpc.GetSubmoduleRequest.base:type_name -> rpc.ReadRequest + 25, // 23: rpc.GetSubmoduleResponse.submodule:type_name -> rpc.Submodule + 41, // 24: rpc.GetCommitDivergencesRequest.base:type_name -> rpc.ReadRequest + 27, // 25: rpc.GetCommitDivergencesRequest.requests:type_name -> rpc.CommitDivergenceRequest + 29, // 26: rpc.GetCommitDivergencesResponse.divergences:type_name -> rpc.CommitDivergence + 39, // 27: rpc.DeleteRepositoryRequest.base:type_name -> rpc.WriteRequest + 39, // 28: rpc.SyncRepositoryRequest.base:type_name -> rpc.WriteRequest + 41, // 29: rpc.HashRepositoryRequest.base:type_name -> rpc.ReadRequest + 2, // 30: rpc.HashRepositoryRequest.hash_type:type_name -> rpc.HashType + 3, // 31: rpc.HashRepositoryRequest.aggregation_type:type_name -> rpc.HashAggregationType + 41, // 32: rpc.MergeBaseRequest.base:type_name -> rpc.ReadRequest + 4, // 33: rpc.RepositoryService.CreateRepository:input_type -> rpc.CreateRepositoryRequest + 7, // 34: rpc.RepositoryService.GetTreeNode:input_type -> rpc.GetTreeNodeRequest + 9, // 35: rpc.RepositoryService.ListTreeNodes:input_type -> rpc.ListTreeNodesRequest + 12, // 36: rpc.RepositoryService.PathsDetails:input_type -> rpc.PathsDetailsRequest + 23, // 37: rpc.RepositoryService.GetSubmodule:input_type -> rpc.GetSubmoduleRequest + 20, // 38: rpc.RepositoryService.GetBlob:input_type -> rpc.GetBlobRequest + 17, // 39: rpc.RepositoryService.ListCommits:input_type -> rpc.ListCommitsRequest + 15, // 40: rpc.RepositoryService.GetCommit:input_type -> rpc.GetCommitRequest + 26, // 41: rpc.RepositoryService.GetCommitDivergences:input_type -> rpc.GetCommitDivergencesRequest + 30, // 42: rpc.RepositoryService.DeleteRepository:input_type -> rpc.DeleteRepositoryRequest + 32, // 43: rpc.RepositoryService.SyncRepository:input_type -> rpc.SyncRepositoryRequest + 34, // 44: rpc.RepositoryService.HashRepository:input_type -> rpc.HashRepositoryRequest + 36, // 45: rpc.RepositoryService.MergeBase:input_type -> rpc.MergeBaseRequest + 6, // 46: rpc.RepositoryService.CreateRepository:output_type -> rpc.CreateRepositoryResponse + 8, // 47: rpc.RepositoryService.GetTreeNode:output_type -> rpc.GetTreeNodeResponse + 10, // 48: rpc.RepositoryService.ListTreeNodes:output_type -> rpc.ListTreeNodesResponse + 13, // 49: rpc.RepositoryService.PathsDetails:output_type -> rpc.PathsDetailsResponse + 24, // 50: rpc.RepositoryService.GetSubmodule:output_type -> rpc.GetSubmoduleResponse + 21, // 51: rpc.RepositoryService.GetBlob:output_type -> rpc.GetBlobResponse + 18, // 52: rpc.RepositoryService.ListCommits:output_type -> rpc.ListCommitsResponse + 16, // 53: rpc.RepositoryService.GetCommit:output_type -> rpc.GetCommitResponse + 28, // 54: rpc.RepositoryService.GetCommitDivergences:output_type -> rpc.GetCommitDivergencesResponse + 31, // 55: rpc.RepositoryService.DeleteRepository:output_type -> rpc.DeleteRepositoryResponse + 33, // 56: rpc.RepositoryService.SyncRepository:output_type -> rpc.SyncRepositoryResponse + 35, // 57: rpc.RepositoryService.HashRepository:output_type -> rpc.HashRepositoryResponse + 37, // 58: rpc.RepositoryService.MergeBase:output_type -> rpc.MergeBaseResponse + 46, // [46:59] is the sub-list for method output_type + 33, // [33:46] is the sub-list for method input_type + 33, // [33:33] is the sub-list for extension type_name + 33, // [33:33] is the sub-list for extension extendee + 0, // [0:33] is the sub-list for field type_name } func init() { file_repo_proto_init() } @@ -2607,7 +2778,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCommitRequest); i { + switch v := v.(*PathsDetailsRequest); i { case 0: return &v.state case 1: @@ -2619,7 +2790,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCommitResponse); i { + switch v := v.(*PathsDetailsResponse); i { case 0: return &v.state case 1: @@ -2631,7 +2802,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListCommitsRequest); i { + switch v := v.(*PathDetails); i { case 0: return &v.state case 1: @@ -2643,7 +2814,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListCommitsResponse); i { + switch v := v.(*GetCommitRequest); i { case 0: return &v.state case 1: @@ -2655,7 +2826,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RenameDetails); i { + switch v := v.(*GetCommitResponse); i { case 0: return &v.state case 1: @@ -2667,7 +2838,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetBlobRequest); i { + switch v := v.(*ListCommitsRequest); i { case 0: return &v.state case 1: @@ -2679,7 +2850,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetBlobResponse); i { + switch v := v.(*ListCommitsResponse); i { case 0: return &v.state case 1: @@ -2691,7 +2862,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetBlobResponseHeader); i { + switch v := v.(*RenameDetails); i { case 0: return &v.state case 1: @@ -2703,7 +2874,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetSubmoduleRequest); i { + switch v := v.(*GetBlobRequest); i { case 0: return &v.state case 1: @@ -2715,7 +2886,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetSubmoduleResponse); i { + switch v := v.(*GetBlobResponse); i { case 0: return &v.state case 1: @@ -2727,7 +2898,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Submodule); i { + switch v := v.(*GetBlobResponseHeader); i { case 0: return &v.state case 1: @@ -2739,7 +2910,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCommitDivergencesRequest); i { + switch v := v.(*GetSubmoduleRequest); i { case 0: return &v.state case 1: @@ -2751,7 +2922,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CommitDivergenceRequest); i { + switch v := v.(*GetSubmoduleResponse); i { case 0: return &v.state case 1: @@ -2763,7 +2934,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCommitDivergencesResponse); i { + switch v := v.(*Submodule); i { case 0: return &v.state case 1: @@ -2775,7 +2946,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CommitDivergence); i { + switch v := v.(*GetCommitDivergencesRequest); i { case 0: return &v.state case 1: @@ -2787,7 +2958,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteRepositoryRequest); i { + switch v := v.(*CommitDivergenceRequest); i { case 0: return &v.state case 1: @@ -2799,7 +2970,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteRepositoryResponse); i { + switch v := v.(*GetCommitDivergencesResponse); i { case 0: return &v.state case 1: @@ -2811,7 +2982,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SyncRepositoryRequest); i { + switch v := v.(*CommitDivergence); i { case 0: return &v.state case 1: @@ -2823,7 +2994,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SyncRepositoryResponse); i { + switch v := v.(*DeleteRepositoryRequest); i { case 0: return &v.state case 1: @@ -2835,7 +3006,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HashRepositoryRequest); i { + switch v := v.(*DeleteRepositoryResponse); i { case 0: return &v.state case 1: @@ -2847,7 +3018,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HashRepositoryResponse); i { + switch v := v.(*SyncRepositoryRequest); i { case 0: return &v.state case 1: @@ -2859,7 +3030,7 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MergeBaseRequest); i { + switch v := v.(*SyncRepositoryResponse); i { case 0: return &v.state case 1: @@ -2871,6 +3042,42 @@ func file_repo_proto_init() { } } file_repo_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HashRepositoryRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_repo_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HashRepositoryResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_repo_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MergeBaseRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_repo_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MergeBaseResponse); i { case 0: return &v.state @@ -2887,7 +3094,7 @@ func file_repo_proto_init() { (*CreateRepositoryRequest_Header)(nil), (*CreateRepositoryRequest_File)(nil), } - file_repo_proto_msgTypes[14].OneofWrappers = []interface{}{ + file_repo_proto_msgTypes[17].OneofWrappers = []interface{}{ (*GetBlobResponse_Header)(nil), (*GetBlobResponse_Content)(nil), } @@ -2897,7 +3104,7 @@ func file_repo_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_repo_proto_rawDesc, NumEnums: 4, - NumMessages: 31, + NumMessages: 34, NumExtensions: 0, NumServices: 1, }, diff --git a/gitrpc/rpc/repo_grpc.pb.go b/gitrpc/rpc/repo_grpc.pb.go index 568e656b7..d3154e88c 100644 --- a/gitrpc/rpc/repo_grpc.pb.go +++ b/gitrpc/rpc/repo_grpc.pb.go @@ -25,6 +25,7 @@ type RepositoryServiceClient interface { CreateRepository(ctx context.Context, opts ...grpc.CallOption) (RepositoryService_CreateRepositoryClient, error) GetTreeNode(ctx context.Context, in *GetTreeNodeRequest, opts ...grpc.CallOption) (*GetTreeNodeResponse, error) ListTreeNodes(ctx context.Context, in *ListTreeNodesRequest, opts ...grpc.CallOption) (RepositoryService_ListTreeNodesClient, error) + PathsDetails(ctx context.Context, in *PathsDetailsRequest, opts ...grpc.CallOption) (*PathsDetailsResponse, error) GetSubmodule(ctx context.Context, in *GetSubmoduleRequest, opts ...grpc.CallOption) (*GetSubmoduleResponse, error) GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (RepositoryService_GetBlobClient, error) ListCommits(ctx context.Context, in *ListCommitsRequest, opts ...grpc.CallOption) (RepositoryService_ListCommitsClient, error) @@ -119,6 +120,15 @@ func (x *repositoryServiceListTreeNodesClient) Recv() (*ListTreeNodesResponse, e return m, nil } +func (c *repositoryServiceClient) PathsDetails(ctx context.Context, in *PathsDetailsRequest, opts ...grpc.CallOption) (*PathsDetailsResponse, error) { + out := new(PathsDetailsResponse) + err := c.cc.Invoke(ctx, "/rpc.RepositoryService/PathsDetails", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *repositoryServiceClient) GetSubmodule(ctx context.Context, in *GetSubmoduleRequest, opts ...grpc.CallOption) (*GetSubmoduleResponse, error) { out := new(GetSubmoduleResponse) err := c.cc.Invoke(ctx, "/rpc.RepositoryService/GetSubmodule", in, out, opts...) @@ -253,6 +263,7 @@ type RepositoryServiceServer interface { CreateRepository(RepositoryService_CreateRepositoryServer) error GetTreeNode(context.Context, *GetTreeNodeRequest) (*GetTreeNodeResponse, error) ListTreeNodes(*ListTreeNodesRequest, RepositoryService_ListTreeNodesServer) error + PathsDetails(context.Context, *PathsDetailsRequest) (*PathsDetailsResponse, error) GetSubmodule(context.Context, *GetSubmoduleRequest) (*GetSubmoduleResponse, error) GetBlob(*GetBlobRequest, RepositoryService_GetBlobServer) error ListCommits(*ListCommitsRequest, RepositoryService_ListCommitsServer) error @@ -278,6 +289,9 @@ func (UnimplementedRepositoryServiceServer) GetTreeNode(context.Context, *GetTre func (UnimplementedRepositoryServiceServer) ListTreeNodes(*ListTreeNodesRequest, RepositoryService_ListTreeNodesServer) error { return status.Errorf(codes.Unimplemented, "method ListTreeNodes not implemented") } +func (UnimplementedRepositoryServiceServer) PathsDetails(context.Context, *PathsDetailsRequest) (*PathsDetailsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PathsDetails not implemented") +} func (UnimplementedRepositoryServiceServer) GetSubmodule(context.Context, *GetSubmoduleRequest) (*GetSubmoduleResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSubmodule not implemented") } @@ -383,6 +397,24 @@ func (x *repositoryServiceListTreeNodesServer) Send(m *ListTreeNodesResponse) er return x.ServerStream.SendMsg(m) } +func _RepositoryService_PathsDetails_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PathsDetailsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RepositoryServiceServer).PathsDetails(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/rpc.RepositoryService/PathsDetails", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RepositoryServiceServer).PathsDetails(ctx, req.(*PathsDetailsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _RepositoryService_GetSubmodule_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetSubmoduleRequest) if err := dec(in); err != nil { @@ -562,6 +594,10 @@ var RepositoryService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetTreeNode", Handler: _RepositoryService_GetTreeNode_Handler, }, + { + MethodName: "PathsDetails", + Handler: _RepositoryService_PathsDetails_Handler, + }, { MethodName: "GetSubmodule", Handler: _RepositoryService_GetSubmodule_Handler, diff --git a/gitrpc/server/config.go b/gitrpc/server/config.go index 46ecedf6a..0df77b296 100644 --- a/gitrpc/server/config.go +++ b/gitrpc/server/config.go @@ -9,6 +9,12 @@ import ( "time" ) +const ( + ModeInMemory = "inmemory" + ModeRedis = "redis" + ModeNone = "none" +) + // Config represents the configuration for the gitrpc server. type Config struct { // Bind specifies the addr used to bind the grpc server. @@ -25,6 +31,25 @@ type Config struct { } MaxConnAge time.Duration `envconfig:"GITRPC_SERVER_MAX_CONN_AGE" default:"630720000s"` MaxConnAgeGrace time.Duration `envconfig:"GITRPC_SERVER_MAX_CONN_AGE_GRACE" default:"630720000s"` + + // LastCommitCache holds configuration options for the last commit cache. + LastCommitCache struct { + // Mode determines where the cache will be. Valid values are "inmemory" (default), "redis" or "none". + Mode string `envconfig:"GITRPC_LAST_COMMIT_CACHE_MODE" default:"inmemory"` + + // DurationSeconds defines cache duration in seconds of last commit, default=12h. + DurationSeconds int `envconfig:"GITRPC_LAST_COMMIT_CACHE_SECONDS" default:"43200"` + } + + Redis struct { + Endpoint string `envconfig:"GITRPC_REDIS_ENDPOINT" default:"localhost:6379"` + MaxRetries int `envconfig:"GITRPC_REDIS_MAX_RETRIES" default:"3"` + MinIdleConnections int `envconfig:"GITRPC_REDIS_MIN_IDLE_CONNECTIONS" default:"0"` + Password string `envconfig:"GITRPC_REDIS_PASSWORD"` + SentinelMode bool `envconfig:"GITRPC_REDIS_USE_SENTINEL" default:"false"` + SentinelMaster string `envconfig:"GITRPC_REDIS_SENTINEL_MASTER"` + SentinelEndpoint string `envconfig:"GITRPC_REDIS_SENTINEL_ENDPOINT"` + } } func (c *Config) Validate() error { @@ -46,6 +71,9 @@ func (c *Config) Validate() error { if c.MaxConnAgeGrace == 0 { return errors.New("config.MaxConnAgeGrace is required") } + if m := c.LastCommitCache.Mode; m != "" && m != ModeInMemory && m != ModeRedis && m != ModeNone { + return errors.New("config.LastCommitCache.Mode has unsupported value") + } return nil } diff --git a/gitrpc/server/wire.go b/gitrpc/server/wire.go index 599c0f771..2b315aefc 100644 --- a/gitrpc/server/wire.go +++ b/gitrpc/server/wire.go @@ -5,9 +5,14 @@ package server import ( + "time" + + "github.com/harness/gitness/cache" "github.com/harness/gitness/gitrpc/internal/gitea" "github.com/harness/gitness/gitrpc/internal/service" + "github.com/harness/gitness/gitrpc/internal/types" + "github.com/go-redis/redis/v8" "github.com/google/wire" ) @@ -16,10 +21,37 @@ var WireSet = wire.NewSet( ProvideServer, ProvideHTTPServer, ProvideGITAdapter, + ProvideGoGitRepoCache, + ProvideLastCommitCache, ) -func ProvideGITAdapter() (service.GitAdapter, error) { - return gitea.New() +func ProvideGoGitRepoCache() cache.Cache[string, *gitea.RepoEntryValue] { + return gitea.NewRepoCache() +} + +func ProvideLastCommitCache( + config Config, + redisClient redis.UniversalClient, + repoCache cache.Cache[string, *gitea.RepoEntryValue], +) cache.Cache[gitea.CommitEntryKey, *types.Commit] { + cacheDuration := time.Duration(config.LastCommitCache.DurationSeconds) * time.Second + + if config.LastCommitCache.Mode == ModeNone || cacheDuration < time.Second { + return gitea.NoLastCommitCache(repoCache) + } + + if config.LastCommitCache.Mode == ModeRedis && redisClient != nil { + return gitea.NewRedisLastCommitCache(redisClient, cacheDuration, repoCache) + } + + return gitea.NewInMemoryLastCommitCache(cacheDuration, repoCache) +} + +func ProvideGITAdapter( + repoCache cache.Cache[string, *gitea.RepoEntryValue], + lastCommitCache cache.Cache[gitea.CommitEntryKey, *types.Commit], +) (service.GitAdapter, error) { + return gitea.New(repoCache, lastCommitCache) } func ProvideServer(config Config, adapter service.GitAdapter) (*GRPCServer, error) { diff --git a/gitrpc/tree.go b/gitrpc/tree.go index 03e51886e..90cca5669 100644 --- a/gitrpc/tree.go +++ b/gitrpc/tree.go @@ -51,16 +51,10 @@ type ListTreeNodeParams struct { GitREF string Path string IncludeLatestCommit bool - Recursive bool } type ListTreeNodeOutput struct { - Nodes []TreeNodeWithCommit -} - -type TreeNodeWithCommit struct { - TreeNode - Commit *Commit + Nodes []TreeNode } type GetTreeNodeParams struct { @@ -94,6 +88,7 @@ func (c *Client) GetTreeNode(ctx context.Context, params *GetTreeNodeParams) (*G if err != nil { return nil, fmt.Errorf("failed to map rpc node: %w", err) } + var commit *Commit if resp.GetCommit() != nil { commit, err = mapRPCCommit(resp.GetCommit()) @@ -113,17 +108,15 @@ func (c *Client) ListTreeNodes(ctx context.Context, params *ListTreeNodeParams) return nil, ErrNoParamsProvided } stream, err := c.repoService.ListTreeNodes(ctx, &rpc.ListTreeNodesRequest{ - Base: mapToRPCReadRequest(params.ReadParams), - GitRef: params.GitREF, - Path: params.Path, - IncludeLatestCommit: params.IncludeLatestCommit, - Recursive: params.Recursive, + Base: mapToRPCReadRequest(params.ReadParams), + GitRef: params.GitREF, + Path: params.Path, }) if err != nil { return nil, fmt.Errorf("failed to start stream for tree nodes: %w", err) } - nodes := make([]TreeNodeWithCommit, 0, 16) + nodes := make([]TreeNode, 0, 16) for { var next *rpc.ListTreeNodesResponse next, err = stream.Recv() @@ -140,21 +133,60 @@ func (c *Client) ListTreeNodes(ctx context.Context, params *ListTreeNodeParams) if err != nil { return nil, fmt.Errorf("failed to map rpc node: %w", err) } - var commit *Commit - if next.GetCommit() != nil { - commit, err = mapRPCCommit(next.GetCommit()) - if err != nil { - return nil, fmt.Errorf("failed to map rpc commit: %w", err) - } - } - nodes = append(nodes, TreeNodeWithCommit{ - TreeNode: node, - Commit: commit, - }) + nodes = append(nodes, node) } return &ListTreeNodeOutput{ Nodes: nodes, }, nil } + +type PathsDetailsParams struct { + ReadParams + GitREF string + Paths []string +} + +type PathsDetailsOutput struct { + Details []PathDetails +} + +type PathDetails struct { + Path string `json:"path"` + LastCommit *Commit `json:"last_commit,omitempty"` + Size int64 `json:"size,omitempty"` +} + +func (c *Client) PathsDetails(ctx context.Context, params PathsDetailsParams) (PathsDetailsOutput, error) { + response, err := c.repoService.PathsDetails(ctx, &rpc.PathsDetailsRequest{ + Base: mapToRPCReadRequest(params.ReadParams), + GitRef: params.GitREF, + Paths: params.Paths, + }) + if err != nil { + return PathsDetailsOutput{}, processRPCErrorf(err, "failed to get paths details") + } + + details := make([]PathDetails, len(response.PathDetails)) + for i, pathDetail := range response.PathDetails { + var lastCommit *Commit + + if pathDetail.LastCommit != nil { + lastCommit, err = mapRPCCommit(pathDetail.LastCommit) + if err != nil { + return PathsDetailsOutput{}, fmt.Errorf("failed to map last commit: %w", err) + } + } + + details[i] = PathDetails{ + Path: pathDetail.Path, + Size: pathDetail.Size, + LastCommit: lastCommit, + } + } + + return PathsDetailsOutput{ + Details: details, + }, nil +} diff --git a/internal/api/auth/pipeline.go b/internal/api/auth/pipeline.go new file mode 100644 index 000000000..8c5c199a6 --- /dev/null +++ b/internal/api/auth/pipeline.go @@ -0,0 +1,29 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package auth + +import ( + "context" + + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// CheckPipeline checks if a repo specific permission is granted for the current auth session +// in the scope of its parent. +// Returns nil if the permission is granted, otherwise returns an error. +// NotAuthenticated, NotAuthorized, or any underlying error. +func CheckPipeline(ctx context.Context, authorizer authz.Authorizer, session *auth.Session, + parentPath, uid string, permission enum.Permission) error { + scope := &types.Scope{SpacePath: parentPath} + resource := &types.Resource{ + Type: enum.ResourceTypePipeline, + Name: uid, + } + + return Check(ctx, authorizer, session, scope, resource, permission) +} diff --git a/internal/api/auth/secret.go b/internal/api/auth/secret.go new file mode 100644 index 000000000..7804f8c09 --- /dev/null +++ b/internal/api/auth/secret.go @@ -0,0 +1,29 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package auth + +import ( + "context" + + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// CheckSecret checks if a repo specific permission is granted for the current auth session +// in the scope of its parent. +// Returns nil if the permission is granted, otherwise returns an error. +// NotAuthenticated, NotAuthorized, or any underlying error. +func CheckSecret(ctx context.Context, authorizer authz.Authorizer, session *auth.Session, + parentPath, uid string, permission enum.Permission) error { + scope := &types.Scope{SpacePath: parentPath} + resource := &types.Resource{ + Type: enum.ResourceTypeSecret, + Name: uid, + } + + return Check(ctx, authorizer, session, scope, resource, permission) +} diff --git a/internal/api/controller/check/check_report.go b/internal/api/controller/check/check_report.go index e9c2a0657..add5408a9 100644 --- a/internal/api/controller/check/check_report.go +++ b/internal/api/controller/check/check_report.go @@ -51,27 +51,29 @@ func (in *ReportInput) Validate() error { } in.Payload.Kind = payloadKind - //nolint:gocritic // more values to follow on the enum (we want linter warning in case it is missed) switch in.Payload.Kind { - case enum.CheckPayloadKindExternal: - // the default external type does not support payload: clear it here + case enum.CheckPayloadKindEmpty: + // the default payload kind (empty) does not support the payload data: clear it here + in.Payload.Version = "" + in.Payload.Data = []byte("{}") - var err error - - if in.Link == "" { // the link is mandatory for the external + if in.Link == "" { // the link is mandatory as there is nothing in the payload return usererror.BadRequest("Link is missing") } + case enum.CheckPayloadKindRaw, enum.CheckPayloadKindMarkdown: + // the text payload kinds (raw and markdown) do not support the version if in.Payload.Version != "" { - return usererror.BadRequest("Payload version must be empty") + return usererror.BadRequestf("Payload version must be empty for the payload kind '%s'", + in.Payload.Kind) } - in.Payload.Data, err = sanitizeJsonPayload(in.Payload.Data, &struct { - Details string `json:"details"` - }{}) + payloadDataJSON, err := sanitizeJsonPayload(in.Payload.Data, &types.CheckPayloadText{}) if err != nil { return err } + + in.Payload.Data = payloadDataJSON } return nil diff --git a/internal/api/controller/execution/controller.go b/internal/api/controller/execution/controller.go new file mode 100644 index 000000000..abfa572eb --- /dev/null +++ b/internal/api/controller/execution/controller.go @@ -0,0 +1,36 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package execution + +import ( + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/internal/store" + + "github.com/jmoiron/sqlx" +) + +type Controller struct { + db *sqlx.DB + authorizer authz.Authorizer + executionStore store.ExecutionStore + pipelineStore store.PipelineStore + spaceStore store.SpaceStore +} + +func NewController( + db *sqlx.DB, + authorizer authz.Authorizer, + executionStore store.ExecutionStore, + pipelineStore store.PipelineStore, + spaceStore store.SpaceStore, +) *Controller { + return &Controller{ + db: db, + authorizer: authorizer, + executionStore: executionStore, + pipelineStore: pipelineStore, + spaceStore: spaceStore, + } +} diff --git a/internal/api/controller/execution/create.go b/internal/api/controller/execution/create.go new file mode 100644 index 000000000..595def806 --- /dev/null +++ b/internal/api/controller/execution/create.go @@ -0,0 +1,66 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package execution + +import ( + "context" + "fmt" + "time" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// TODO: Add more as needed. +type CreateInput struct { + Status string `json:"status"` +} + +func (c *Controller) Create( + ctx context.Context, + session *auth.Session, + spaceRef string, + uid string, + in *CreateInput, +) (*types.Execution, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, fmt.Errorf("could not find space: %w", err) + } + + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) + if err != nil { + return nil, fmt.Errorf("could not find pipeline: %w", err) + } + + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineExecute) + if err != nil { + return nil, fmt.Errorf("failed to authorize: %w", err) + } + + pipeline, err = c.pipelineStore.IncrementSeqNum(ctx, pipeline) + if err != nil { + return nil, fmt.Errorf("failed to increment sequence number: %w", err) + } + + now := time.Now().UnixMilli() + execution := &types.Execution{ + Number: pipeline.Seq, + Status: in.Status, + RepoID: pipeline.RepoID, + PipelineID: pipeline.ID, + Created: now, + Updated: now, + Version: 0, + } + err = c.executionStore.Create(ctx, execution) + if err != nil { + return nil, fmt.Errorf("execution creation failed: %w", err) + } + + return execution, nil +} diff --git a/internal/api/controller/execution/delete.go b/internal/api/controller/execution/delete.go new file mode 100644 index 000000000..9b2a84aa9 --- /dev/null +++ b/internal/api/controller/execution/delete.go @@ -0,0 +1,41 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package execution + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types/enum" +) + +func (c *Controller) Delete( + ctx context.Context, + session *auth.Session, + spaceRef string, + pipelineUID string, + executionNum int64, +) error { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return fmt.Errorf("could not find parent space: %w", err) + } + + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, pipelineUID) + if err != nil { + return fmt.Errorf("could not find pipeline: %w", err) + } + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineDelete) + if err != nil { + return fmt.Errorf("could not authorize: %w", err) + } + err = c.executionStore.Delete(ctx, pipeline.ID, executionNum) + if err != nil { + return fmt.Errorf("could not delete execution: %w", err) + } + return nil +} diff --git a/internal/api/controller/execution/find.go b/internal/api/controller/execution/find.go new file mode 100644 index 000000000..d597488fd --- /dev/null +++ b/internal/api/controller/execution/find.go @@ -0,0 +1,40 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package execution + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +func (c *Controller) Find( + ctx context.Context, + session *auth.Session, + spaceRef string, + pipelineUID string, + executionNum int64, +) (*types.Execution, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, fmt.Errorf("could not find parent space: %w", err) + } + + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, pipelineUID) + if err != nil { + return nil, fmt.Errorf("could not find pipeline: %w", err) + } + + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineView) + if err != nil { + return nil, fmt.Errorf("could not authorize: %w", err) + } + + return c.executionStore.Find(ctx, pipeline.ID, executionNum) +} diff --git a/internal/api/controller/execution/list.go b/internal/api/controller/execution/list.go new file mode 100644 index 000000000..a5ba72f6b --- /dev/null +++ b/internal/api/controller/execution/list.go @@ -0,0 +1,59 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. +package execution + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/store/database/dbtx" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +func (c *Controller) List( + ctx context.Context, + session *auth.Session, + spaceRef string, + pipelineUID string, + pagination types.Pagination, +) ([]*types.Execution, int64, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, 0, fmt.Errorf("failed to find parent space: %w", err) + } + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, pipelineUID) + if err != nil { + return nil, 0, fmt.Errorf("failed to find pipeline: %w", err) + } + + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineView) + if err != nil { + return nil, 0, fmt.Errorf("failed to authorize: %w", err) + } + + var count int64 + var executions []*types.Execution + + err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { + count, err = c.executionStore.Count(ctx, pipeline.ID) + if err != nil { + return fmt.Errorf("failed to count child executions: %w", err) + } + + executions, err = c.executionStore.List(ctx, pipeline.ID, pagination) + if err != nil { + return fmt.Errorf("failed to list child executions: %w", err) + } + + return + }, dbtx.TxDefaultReadOnly) + if err != nil { + return executions, count, fmt.Errorf("failed to fetch list: %w", err) + } + + return executions, count, nil +} diff --git a/internal/api/controller/execution/update.go b/internal/api/controller/execution/update.go new file mode 100644 index 000000000..0bd8a8959 --- /dev/null +++ b/internal/api/controller/execution/update.go @@ -0,0 +1,57 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package execution + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +type UpdateInput struct { + Status string `json:"status"` +} + +func (c *Controller) Update( + ctx context.Context, + session *auth.Session, + spaceRef string, + pipelineUID string, + executionNum int64, + in *UpdateInput) (*types.Execution, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, fmt.Errorf("could not find space: %w", err) + } + + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipelineUID, enum.PermissionPipelineEdit) + if err != nil { + return nil, fmt.Errorf("failed to check auth: %w", err) + } + + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, pipelineUID) + if err != nil { + return nil, fmt.Errorf("failed to find pipeline: %w", err) + } + + execution, err := c.executionStore.Find(ctx, pipeline.ID, executionNum) + if err != nil { + return nil, fmt.Errorf("failed to find execution: %w", err) + } + + return c.executionStore.UpdateOptLock(ctx, + execution, func(original *types.Execution) error { + // update values only if provided + if in.Status != "" { + original.Status = in.Status + } + + return nil + }) +} diff --git a/internal/api/controller/execution/wire.go b/internal/api/controller/execution/wire.go new file mode 100644 index 000000000..c559831f9 --- /dev/null +++ b/internal/api/controller/execution/wire.go @@ -0,0 +1,27 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package execution + +import ( + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/internal/store" + + "github.com/google/wire" + "github.com/jmoiron/sqlx" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideController, +) + +func ProvideController(db *sqlx.DB, + authorizer authz.Authorizer, + executionStore store.ExecutionStore, + pipelineStore store.PipelineStore, + spaceStore store.SpaceStore, +) *Controller { + return NewController(db, authorizer, executionStore, pipelineStore, spaceStore) +} diff --git a/internal/api/controller/pipeline/controller.go b/internal/api/controller/pipeline/controller.go new file mode 100644 index 000000000..61020c78a --- /dev/null +++ b/internal/api/controller/pipeline/controller.go @@ -0,0 +1,44 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pipeline + +import ( + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/internal/store" + "github.com/harness/gitness/types/check" + + "github.com/jmoiron/sqlx" +) + +type Controller struct { + defaultBranch string + db *sqlx.DB + uidCheck check.PathUID + pathStore store.PathStore + repoStore store.RepoStore + authorizer authz.Authorizer + pipelineStore store.PipelineStore + spaceStore store.SpaceStore +} + +func NewController( + db *sqlx.DB, + uidCheck check.PathUID, + authorizer authz.Authorizer, + pathStore store.PathStore, + repoStore store.RepoStore, + pipelineStore store.PipelineStore, + spaceStore store.SpaceStore, +) *Controller { + return &Controller{ + db: db, + uidCheck: uidCheck, + pathStore: pathStore, + repoStore: repoStore, + authorizer: authorizer, + pipelineStore: pipelineStore, + spaceStore: spaceStore, + } +} diff --git a/internal/api/controller/pipeline/create.go b/internal/api/controller/pipeline/create.go new file mode 100644 index 000000000..74cbedc8d --- /dev/null +++ b/internal/api/controller/pipeline/create.go @@ -0,0 +1,106 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pipeline + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/check" + "github.com/harness/gitness/types/enum" +) + +var ( + // errPipelineRequiresParent if the user tries to create a pipeline without a parent space. + errPipelineRequiresParent = usererror.BadRequest( + "Parent space required - standalone pipelines are not supported.") +) + +type CreateInput struct { + Description string `json:"description"` + SpaceRef string `json:"space_ref"` + UID string `json:"uid"` + RepoRef string `json:"repo_ref"` // empty if repo_type != gitness + RepoType enum.ScmType `json:"repo_type"` + DefaultBranch string `json:"default_branch"` + ConfigPath string `json:"config_path"` +} + +func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*types.Pipeline, error) { + parentSpace, err := c.spaceStore.FindByRef(ctx, in.SpaceRef) + if err != nil { + return nil, fmt.Errorf("could not find parent by ref: %w", err) + } + + err = apiauth.CheckPipeline(ctx, c.authorizer, session, parentSpace.Path, in.UID, enum.PermissionPipelineEdit) + if err != nil { + return nil, err + } + + var repoID int64 + + if in.RepoType == enum.ScmTypeGitness { + repo, err := c.repoStore.FindByRef(ctx, in.RepoRef) + if err != nil { + return nil, fmt.Errorf("could not find repo by ref: %w", err) + } + repoID = repo.ID + } + + if err := c.sanitizeCreateInput(in); err != nil { + return nil, fmt.Errorf("failed to sanitize input: %w", err) + } + + var pipeline *types.Pipeline + now := time.Now().UnixMilli() + pipeline = &types.Pipeline{ + Description: in.Description, + SpaceID: parentSpace.ID, + UID: in.UID, + Seq: 0, + RepoID: repoID, + RepoType: in.RepoType, + DefaultBranch: in.DefaultBranch, + ConfigPath: in.ConfigPath, + Created: now, + Updated: now, + Version: 0, + } + err = c.pipelineStore.Create(ctx, pipeline) + if err != nil { + return nil, fmt.Errorf("pipeline creation failed: %w", err) + } + + return pipeline, nil +} + +func (c *Controller) sanitizeCreateInput(in *CreateInput) error { + parentRefAsID, err := strconv.ParseInt(in.SpaceRef, 10, 64) + if (err == nil && parentRefAsID <= 0) || (len(strings.TrimSpace(in.SpaceRef)) == 0) { + return errPipelineRequiresParent + } + + if err := c.uidCheck(in.UID, false); err != nil { + return err + } + + in.Description = strings.TrimSpace(in.Description) + if err := check.Description(in.Description); err != nil { + return err + } + + if in.DefaultBranch == "" { + in.DefaultBranch = c.defaultBranch + } + + return nil +} diff --git a/internal/api/controller/pipeline/delete.go b/internal/api/controller/pipeline/delete.go new file mode 100644 index 000000000..cdae2a3cc --- /dev/null +++ b/internal/api/controller/pipeline/delete.go @@ -0,0 +1,31 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pipeline + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types/enum" +) + +func (c *Controller) Delete(ctx context.Context, session *auth.Session, spaceRef string, uid string) error { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return fmt.Errorf("could not find parent space: %w", err) + } + + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineDelete) + if err != nil { + return fmt.Errorf("could not authorize: %w", err) + } + err = c.pipelineStore.DeleteByUID(ctx, space.ID, uid) + if err != nil { + return fmt.Errorf("could not delete pipeline: %w", err) + } + return nil +} diff --git a/internal/api/controller/pipeline/find.go b/internal/api/controller/pipeline/find.go new file mode 100644 index 000000000..b065bc404 --- /dev/null +++ b/internal/api/controller/pipeline/find.go @@ -0,0 +1,32 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pipeline + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +func (c *Controller) Find( + ctx context.Context, + session *auth.Session, + spaceRef string, + uid string, +) (*types.Pipeline, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, fmt.Errorf("could not find parent space: %w", err) + } + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineView) + if err != nil { + return nil, fmt.Errorf("could not authorize: %w", err) + } + return c.pipelineStore.FindByUID(ctx, space.ID, uid) +} diff --git a/internal/api/controller/pipeline/update.go b/internal/api/controller/pipeline/update.go new file mode 100644 index 000000000..7cc75cdc9 --- /dev/null +++ b/internal/api/controller/pipeline/update.go @@ -0,0 +1,58 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pipeline + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +type UpdateInput struct { + Description string `json:"description"` + UID string `json:"uid"` + ConfigPath string `json:"config_path"` +} + +func (c *Controller) Update( + ctx context.Context, + session *auth.Session, + spaceRef string, + uid string, + in *UpdateInput, +) (*types.Pipeline, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, fmt.Errorf("could not find parent space: %w", err) + } + + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineEdit) + if err != nil { + return nil, fmt.Errorf("could not authorize: %w", err) + } + + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) + if err != nil { + return nil, fmt.Errorf("could not find pipeline: %w", err) + } + + return c.pipelineStore.UpdateOptLock(ctx, pipeline, func(pipeline *types.Pipeline) error { + if in.Description != "" { + pipeline.Description = in.Description + } + if in.UID != "" { + pipeline.UID = in.UID + } + if in.ConfigPath != "" { + pipeline.ConfigPath = in.ConfigPath + } + + return nil + }) +} diff --git a/internal/api/controller/pipeline/wire.go b/internal/api/controller/pipeline/wire.go new file mode 100644 index 000000000..d665a8ee6 --- /dev/null +++ b/internal/api/controller/pipeline/wire.go @@ -0,0 +1,30 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pipeline + +import ( + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/internal/store" + "github.com/harness/gitness/types/check" + + "github.com/google/wire" + "github.com/jmoiron/sqlx" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideController, +) + +func ProvideController(db *sqlx.DB, + uidCheck check.PathUID, + pathStore store.PathStore, + repoStore store.RepoStore, + authorizer authz.Authorizer, + pipelineStore store.PipelineStore, + spaceStore store.SpaceStore, +) *Controller { + return NewController(db, uidCheck, authorizer, pathStore, repoStore, pipelineStore, spaceStore) +} diff --git a/internal/api/controller/pullreq/comment_status.go b/internal/api/controller/pullreq/comment_status.go index ee57577f4..d6818ab41 100644 --- a/internal/api/controller/pullreq/comment_status.go +++ b/internal/api/controller/pullreq/comment_status.go @@ -73,12 +73,11 @@ func (c *Controller) CommentStatus( return nil } - now := time.Now().UnixMilli() - act.Edited = now - act.Resolved = nil act.ResolvedBy = nil + now := time.Now().UnixMilli() + if in.Status != enum.PullReqCommentStatusActive { // In the future if we add more comment resolved statuses // we'll add the ResolvedReason field and put the reason there. diff --git a/internal/api/controller/repo/get_content.go b/internal/api/controller/repo/content_get.go similarity index 78% rename from internal/api/controller/repo/get_content.go rename to internal/api/controller/repo/content_get.go index 039f453d3..5ca0f7d98 100644 --- a/internal/api/controller/repo/get_content.go +++ b/internal/api/controller/repo/content_get.go @@ -80,13 +80,15 @@ type SubmoduleContent struct { func (c *SubmoduleContent) isContent() {} -/* - * GetContent finds the content of the repo at the given path. - * If no gitRef is provided, the content is retrieved from the default branch. - * If includeLatestCommit is enabled, the response contains information of the latest commit that changed the object. - */ -func (c *Controller) GetContent(ctx context.Context, session *auth.Session, repoRef string, - gitRef string, repoPath string, includeLatestCommit bool) (*GetContentOutput, error) { +// GetContent finds the content of the repo at the given path. +// If no gitRef is provided, the content is retrieved from the default branch. +func (c *Controller) GetContent(ctx context.Context, + session *auth.Session, + repoRef string, + gitRef string, + repoPath string, + includeLatestCommit bool, +) (*GetContentOutput, error) { repo, err := c.repoStore.FindByRef(ctx, repoRef) if err != nil { return nil, err @@ -114,7 +116,7 @@ func (c *Controller) GetContent(ctx context.Context, session *auth.Session, repo return nil, err } - info, err := mapToContentInfo(&treeNodeOutput.Node, treeNodeOutput.Commit) + info, err := mapToContentInfo(treeNodeOutput.Node, treeNodeOutput.Commit, includeLatestCommit) if err != nil { return nil, err } @@ -122,8 +124,7 @@ func (c *Controller) GetContent(ctx context.Context, session *auth.Session, repo var content Content switch info.Type { case ContentTypeDir: - // for getContent we don't want any recursiveness for dir content. - content, err = c.getDirContent(ctx, readParams, gitRef, repoPath, includeLatestCommit, false) + content, err = c.getDirContent(ctx, readParams, gitRef, repoPath, includeLatestCommit) case ContentTypeFile: content, err = c.getFileContent(ctx, readParams, info.SHA) case ContentTypeSymlink: @@ -139,13 +140,17 @@ func (c *Controller) GetContent(ctx context.Context, session *auth.Session, repo } return &GetContentOutput{ - ContentInfo: *info, + ContentInfo: info, Content: content, }, nil } -func (c *Controller) getSubmoduleContent(ctx context.Context, readParams gitrpc.ReadParams, gitRef string, - repoPath string, commitSHA string) (*SubmoduleContent, error) { +func (c *Controller) getSubmoduleContent(ctx context.Context, + readParams gitrpc.ReadParams, + gitRef string, + repoPath string, + commitSHA string, +) (*SubmoduleContent, error) { output, err := c.gitRPCClient.GetSubmodule(ctx, &gitrpc.GetSubmoduleParams{ ReadParams: readParams, GitREF: gitRef, @@ -163,8 +168,10 @@ func (c *Controller) getSubmoduleContent(ctx context.Context, readParams gitrpc. }, nil } -func (c *Controller) getFileContent(ctx context.Context, readParams gitrpc.ReadParams, - blobSHA string) (*FileContent, error) { +func (c *Controller) getFileContent(ctx context.Context, + readParams gitrpc.ReadParams, + blobSHA string, +) (*FileContent, error) { output, err := c.gitRPCClient.GetBlob(ctx, &gitrpc.GetBlobParams{ ReadParams: readParams, SHA: blobSHA, @@ -187,8 +194,10 @@ func (c *Controller) getFileContent(ctx context.Context, readParams gitrpc.ReadP }, nil } -func (c *Controller) getSymlinkContent(ctx context.Context, readParams gitrpc.ReadParams, - blobSHA string) (*SymlinkContent, error) { +func (c *Controller) getSymlinkContent(ctx context.Context, + readParams gitrpc.ReadParams, + blobSHA string, +) (*SymlinkContent, error) { output, err := c.gitRPCClient.GetBlob(ctx, &gitrpc.GetBlobParams{ ReadParams: readParams, SHA: blobSHA, @@ -211,14 +220,17 @@ func (c *Controller) getSymlinkContent(ctx context.Context, readParams gitrpc.Re }, nil } -func (c *Controller) getDirContent(ctx context.Context, readParams gitrpc.ReadParams, gitRef string, - repoPath string, includeLatestCommit bool, recursive bool) (*DirContent, error) { +func (c *Controller) getDirContent(ctx context.Context, + readParams gitrpc.ReadParams, + gitRef string, + repoPath string, + includeLatestCommit bool, +) (*DirContent, error) { output, err := c.gitRPCClient.ListTreeNodes(ctx, &gitrpc.ListTreeNodeParams{ ReadParams: readParams, GitREF: gitRef, Path: repoPath, IncludeLatestCommit: includeLatestCommit, - Recursive: recursive, }) if err != nil { // TODO: handle not found error @@ -227,15 +239,11 @@ func (c *Controller) getDirContent(ctx context.Context, readParams gitrpc.ReadPa } entries := make([]ContentInfo, len(output.Nodes)) - for i := range output.Nodes { - node := output.Nodes[i] - - var entry *ContentInfo - entry, err = mapToContentInfo(&node.TreeNode, node.Commit) + for i, node := range output.Nodes { + entries[i], err = mapToContentInfo(node, nil, false) if err != nil { return nil, err } - entries[i] = *entry } return &DirContent{ @@ -243,17 +251,13 @@ func (c *Controller) getDirContent(ctx context.Context, readParams gitrpc.ReadPa }, nil } -func mapToContentInfo(node *gitrpc.TreeNode, commit *gitrpc.Commit) (*ContentInfo, error) { - // node data is expected - if node == nil { - return nil, fmt.Errorf("node can't be nil") - } +func mapToContentInfo(node gitrpc.TreeNode, commit *gitrpc.Commit, includeLatestCommit bool) (ContentInfo, error) { typ, err := mapNodeModeToContentType(node.Mode) if err != nil { - return nil, err + return ContentInfo{}, err } - res := &ContentInfo{ + res := ContentInfo{ Type: typ, SHA: node.SHA, Name: node.Name, @@ -261,10 +265,10 @@ func mapToContentInfo(node *gitrpc.TreeNode, commit *gitrpc.Commit) (*ContentInf } // parse commit only if available - if commit != nil { + if commit != nil && includeLatestCommit { res.LatestCommit, err = controller.MapCommit(commit) if err != nil { - return nil, err + return ContentInfo{}, err } } diff --git a/internal/api/controller/repo/content_paths_details.go b/internal/api/controller/repo/content_paths_details.go new file mode 100644 index 000000000..b42d9d71b --- /dev/null +++ b/internal/api/controller/repo/content_paths_details.go @@ -0,0 +1,70 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package repo + +import ( + "context" + + "github.com/harness/gitness/gitrpc" + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types/enum" +) + +type PathsDetailsInput struct { + Paths []string `json:"paths"` +} + +type PathsDetailsOutput struct { + Details []gitrpc.PathDetails `json:"details"` +} + +// PathsDetails finds the additional info about the provided paths of the repo. +// If no gitRef is provided, the content is retrieved from the default branch. +func (c *Controller) PathsDetails(ctx context.Context, + session *auth.Session, + repoRef string, + gitRef string, + input PathsDetailsInput, +) (PathsDetailsOutput, error) { + repo, err := c.repoStore.FindByRef(ctx, repoRef) + if err != nil { + return PathsDetailsOutput{}, err + } + + if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, true); err != nil { + return PathsDetailsOutput{}, err + } + + if len(input.Paths) == 0 { + return PathsDetailsOutput{}, nil + } + + if len(input.Paths) > 50 { + return PathsDetailsOutput{}, usererror.BadRequest("maximum number of elements in the Paths array is 25") + } + + // set gitRef to default branch in case an empty reference was provided + if gitRef == "" { + gitRef = repo.DefaultBranch + } + + // create read params once + readParams := CreateRPCReadParams(repo) + + result, err := c.gitRPCClient.PathsDetails(ctx, gitrpc.PathsDetailsParams{ + ReadParams: readParams, + GitREF: gitRef, + Paths: input.Paths, + }) + if err != nil { + return PathsDetailsOutput{}, err + } + + return PathsDetailsOutput{ + Details: result.Details, + }, nil +} diff --git a/internal/api/controller/repo/create_branch.go b/internal/api/controller/repo/create_branch.go index 0bc01ae45..3e78f36f2 100644 --- a/internal/api/controller/repo/create_branch.go +++ b/internal/api/controller/repo/create_branch.go @@ -10,9 +10,7 @@ import ( "github.com/harness/gitness/gitrpc" apiauth "github.com/harness/gitness/internal/api/auth" - "github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/auth" - "github.com/harness/gitness/types/check" "github.com/harness/gitness/types/enum" ) @@ -42,11 +40,6 @@ func (c *Controller) CreateBranch(ctx context.Context, session *auth.Session, in.Target = repo.DefaultBranch } - err = checkBranchName(in.Name) - if err != nil { - return nil, fmt.Errorf("branch name failed check: %w", err) - } - writeParams, err := CreateRPCWriteParams(ctx, c.urlProvider, session, repo) if err != nil { return nil, fmt.Errorf("failed to create RPC write params: %w", err) @@ -68,15 +61,3 @@ func (c *Controller) CreateBranch(ctx context.Context, session *auth.Session, return &branch, nil } - -// checkBranchName does some basic branch validation -// We only ensure there are no control characters, the rest is up to git. -// TODO: Do we need some more validation here? -func checkBranchName(name string) error { - // fail fast on missing name - if len(name) == 0 { - return usererror.ErrBadRequest - } - - return check.ForControlCharacters(name) -} diff --git a/internal/api/controller/repo/list_commits.go b/internal/api/controller/repo/list_commits.go index 6ba6d3eb1..d4994034c 100644 --- a/internal/api/controller/repo/list_commits.go +++ b/internal/api/controller/repo/list_commits.go @@ -20,14 +20,14 @@ import ( * ListCommits lists the commits of a repo. */ func (c *Controller) ListCommits(ctx context.Context, session *auth.Session, - repoRef string, gitRef string, filter *types.CommitFilter) ([]types.Commit, []types.RenameDetails, error) { + repoRef string, gitRef string, filter *types.CommitFilter) (types.ListCommitResponse, error) { repo, err := c.repoStore.FindByRef(ctx, repoRef) if err != nil { - return nil, nil, err + return types.ListCommitResponse{}, err } if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil { - return nil, nil, err + return types.ListCommitResponse{}, err } // set gitRef to default branch in case an empty reference was provided @@ -47,7 +47,7 @@ func (c *Controller) ListCommits(ctx context.Context, session *auth.Session, Committer: filter.Committer, }) if err != nil { - return nil, nil, err + return types.ListCommitResponse{}, err } commits := make([]types.Commit, len(rpcOut.Commits)) @@ -55,7 +55,7 @@ func (c *Controller) ListCommits(ctx context.Context, session *auth.Session, var commit *types.Commit commit, err = controller.MapCommit(&rpcOut.Commits[i]) if err != nil { - return nil, nil, fmt.Errorf("failed to map commit: %w", err) + return types.ListCommitResponse{}, fmt.Errorf("failed to map commit: %w", err) } commits[i] = *commit } @@ -64,9 +64,13 @@ func (c *Controller) ListCommits(ctx context.Context, session *auth.Session, for i := range rpcOut.RenameDetails { renameDetails := controller.MapRenameDetails(rpcOut.RenameDetails[i]) if renameDetails == nil { - return nil, nil, fmt.Errorf("rename details was nil") + return types.ListCommitResponse{}, fmt.Errorf("rename details was nil") } renameDetailList[i] = *renameDetails } - return commits, renameDetailList, nil + return types.ListCommitResponse{ + Commits: commits, + RenameDetails: renameDetailList, + TotalCommits: rpcOut.TotalCommits, + }, nil } diff --git a/internal/api/controller/secret/controller.go b/internal/api/controller/secret/controller.go new file mode 100644 index 000000000..5a62633b2 --- /dev/null +++ b/internal/api/controller/secret/controller.go @@ -0,0 +1,44 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package secret + +import ( + "github.com/harness/gitness/encrypt" + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/internal/store" + "github.com/harness/gitness/types/check" + + "github.com/jmoiron/sqlx" +) + +type Controller struct { + db *sqlx.DB + uidCheck check.PathUID + pathStore store.PathStore + encrypter encrypt.Encrypter + secretStore store.SecretStore + authorizer authz.Authorizer + spaceStore store.SpaceStore +} + +func NewController( + db *sqlx.DB, + uidCheck check.PathUID, + authorizer authz.Authorizer, + pathStore store.PathStore, + encrypter encrypt.Encrypter, + secretStore store.SecretStore, + spaceStore store.SpaceStore, +) *Controller { + return &Controller{ + db: db, + uidCheck: uidCheck, + pathStore: pathStore, + encrypter: encrypter, + secretStore: secretStore, + authorizer: authorizer, + spaceStore: spaceStore, + } +} diff --git a/internal/api/controller/secret/create.go b/internal/api/controller/secret/create.go new file mode 100644 index 000000000..18b1f6288 --- /dev/null +++ b/internal/api/controller/secret/create.go @@ -0,0 +1,122 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package secret + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + "github.com/harness/gitness/encrypt" + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/check" + "github.com/harness/gitness/types/enum" +) + +var ( + // errSecretRequiresParent if the user tries to create a secret without a parent space. + errSecretRequiresParent = usererror.BadRequest( + "Parent space required - standalone secret are not supported.") +) + +type CreateInput struct { + Description string `json:"description"` + SpaceRef string `json:"space_ref"` // Ref of the parent space + UID string `json:"uid"` + Data string `json:"data"` +} + +func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*types.Secret, error) { + parentSpace, err := c.spaceStore.FindByRef(ctx, in.SpaceRef) + if err != nil { + return nil, fmt.Errorf("could not find parent by ref: %w", err) + } + + err = apiauth.CheckSecret(ctx, c.authorizer, session, parentSpace.Path, in.UID, enum.PermissionSecretEdit) + if err != nil { + return nil, err + } + + if err := c.sanitizeCreateInput(in); err != nil { + return nil, fmt.Errorf("failed to sanitize input: %w", err) + } + + var secret *types.Secret + now := time.Now().UnixMilli() + secret = &types.Secret{ + Description: in.Description, + Data: in.Data, + SpaceID: parentSpace.ID, + UID: in.UID, + Created: now, + Updated: now, + Version: 0, + } + secret, err = enc(c.encrypter, secret) + if err != nil { + return nil, fmt.Errorf("could not encrypt secret: %w", err) + } + err = c.secretStore.Create(ctx, secret) + if err != nil { + return nil, fmt.Errorf("secret creation failed: %w", err) + } + if err != nil { + return nil, err + } + + return secret, nil +} + +func (c *Controller) sanitizeCreateInput(in *CreateInput) error { + parentRefAsID, err := strconv.ParseInt(in.SpaceRef, 10, 64) + + if (err == nil && parentRefAsID <= 0) || (len(strings.TrimSpace(in.SpaceRef)) == 0) { + return errSecretRequiresParent + } + + if err := c.uidCheck(in.UID, false); err != nil { + return err + } + + in.Description = strings.TrimSpace(in.Description) + if err := check.Description(in.Description); err != nil { + return err + } + + return nil +} + +// helper function returns the same secret with encrypted data. +func enc(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { + if secret == nil { + return nil, fmt.Errorf("cannot encrypt a nil secret") + } + s := *secret + ciphertext, err := encrypt.Encrypt(secret.Data) + if err != nil { + return nil, err + } + s.Data = string(ciphertext) + return &s, nil +} + +// helper function returns the same secret with decrypted data. +func dec(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { + if secret == nil { + return nil, fmt.Errorf("cannot decrypt a nil secret") + } + s := *secret + plaintext, err := encrypt.Decrypt([]byte(secret.Data)) + if err != nil { + return nil, err + } + s.Data = plaintext + return &s, nil +} diff --git a/internal/api/controller/secret/delete.go b/internal/api/controller/secret/delete.go new file mode 100644 index 000000000..bcb31e1be --- /dev/null +++ b/internal/api/controller/secret/delete.go @@ -0,0 +1,31 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package secret + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types/enum" +) + +func (c *Controller) Delete(ctx context.Context, session *auth.Session, spaceRef string, uid string) error { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return fmt.Errorf("could not find space: %w", err) + } + + err = apiauth.CheckSecret(ctx, c.authorizer, session, space.Path, uid, enum.PermissionSecretDelete) + if err != nil { + return fmt.Errorf("failed to authorize: %w", err) + } + err = c.secretStore.DeleteByUID(ctx, space.ID, uid) + if err != nil { + return fmt.Errorf("could not delete secret: %w", err) + } + return nil +} diff --git a/internal/api/controller/secret/find.go b/internal/api/controller/secret/find.go new file mode 100644 index 000000000..34dcc9f3d --- /dev/null +++ b/internal/api/controller/secret/find.go @@ -0,0 +1,40 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package secret + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +func (c *Controller) Find( + ctx context.Context, + session *auth.Session, + spaceRef string, + uid string, +) (*types.Secret, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, fmt.Errorf("could not find space: %w", err) + } + err = apiauth.CheckSecret(ctx, c.authorizer, session, space.Path, uid, enum.PermissionSecretView) + if err != nil { + return nil, fmt.Errorf("failed to authorize: %w", err) + } + secret, err := c.secretStore.FindByUID(ctx, space.ID, uid) + if err != nil { + return nil, fmt.Errorf("could not find secret: %w", err) + } + secret, err = dec(c.encrypter, secret) + if err != nil { + return nil, fmt.Errorf("could not decrypt secret: %w", err) + } + return secret, nil +} diff --git a/internal/api/controller/secret/update.go b/internal/api/controller/secret/update.go new file mode 100644 index 000000000..c3be74121 --- /dev/null +++ b/internal/api/controller/secret/update.go @@ -0,0 +1,63 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package secret + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// UpdateInput is used for updating a repo. +type UpdateInput struct { + Description string `json:"description"` + UID string `json:"uid"` + Data string `json:"data"` +} + +func (c *Controller) Update( + ctx context.Context, + session *auth.Session, + spaceRef string, + uid string, + in *UpdateInput, +) (*types.Secret, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, fmt.Errorf("could not find space: %w", err) + } + + err = apiauth.CheckSecret(ctx, c.authorizer, session, space.Path, uid, enum.PermissionSecretEdit) + if err != nil { + return nil, fmt.Errorf("failed to authorize: %w", err) + } + + secret, err := c.secretStore.FindByUID(ctx, space.ID, uid) + if err != nil { + return nil, fmt.Errorf("could not find secret: %w", err) + } + + return c.secretStore.UpdateOptLock(ctx, secret, func(original *types.Secret) error { + if in.Description != "" { + original.Description = in.Description + } + if in.Data != "" { + data, err := c.encrypter.Encrypt(original.Data) + if err != nil { + return fmt.Errorf("could not encrypt secret: %w", err) + } + original.Data = string(data) + } + if in.UID != "" { + original.UID = in.UID + } + + return nil + }) +} diff --git a/internal/api/controller/secret/wire.go b/internal/api/controller/secret/wire.go new file mode 100644 index 000000000..807cac0d8 --- /dev/null +++ b/internal/api/controller/secret/wire.go @@ -0,0 +1,31 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package secret + +import ( + "github.com/harness/gitness/encrypt" + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/internal/store" + "github.com/harness/gitness/types/check" + + "github.com/google/wire" + "github.com/jmoiron/sqlx" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideController, +) + +func ProvideController(db *sqlx.DB, + uidCheck check.PathUID, + pathStore store.PathStore, + encrypter encrypt.Encrypter, + secretStore store.SecretStore, + authorizer authz.Authorizer, + spaceStore store.SpaceStore, +) *Controller { + return NewController(db, uidCheck, authorizer, pathStore, encrypter, secretStore, spaceStore) +} diff --git a/internal/api/controller/space/controller.go b/internal/api/controller/space/controller.go index 25f1b626f..3249c82e1 100644 --- a/internal/api/controller/space/controller.go +++ b/internal/api/controller/space/controller.go @@ -20,6 +20,8 @@ type Controller struct { uidCheck check.PathUID authorizer authz.Authorizer pathStore store.PathStore + pipelineStore store.PipelineStore + secretStore store.SecretStore spaceStore store.SpaceStore repoStore store.RepoStore principalStore store.PrincipalStore @@ -29,9 +31,9 @@ type Controller struct { func NewController(db *sqlx.DB, urlProvider *url.Provider, uidCheck check.PathUID, authorizer authz.Authorizer, - pathStore store.PathStore, spaceStore store.SpaceStore, - repoStore store.RepoStore, principalStore store.PrincipalStore, repoCtrl *repo.Controller, - membershipStore store.MembershipStore, + pathStore store.PathStore, pipelineStore store.PipelineStore, secretStore store.SecretStore, + spaceStore store.SpaceStore, repoStore store.RepoStore, principalStore store.PrincipalStore, + repoCtrl *repo.Controller, membershipStore store.MembershipStore, ) *Controller { return &Controller{ db: db, @@ -39,6 +41,8 @@ func NewController(db *sqlx.DB, urlProvider *url.Provider, uidCheck: uidCheck, authorizer: authorizer, pathStore: pathStore, + pipelineStore: pipelineStore, + secretStore: secretStore, spaceStore: spaceStore, repoStore: repoStore, principalStore: principalStore, diff --git a/internal/api/controller/space/list_pipelines.go b/internal/api/controller/space/list_pipelines.go new file mode 100644 index 000000000..87d999bb4 --- /dev/null +++ b/internal/api/controller/space/list_pipelines.go @@ -0,0 +1,54 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. +package space + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/store/database/dbtx" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// ListPipelines lists the pipelines in a space. +func (c *Controller) ListPipelines( + ctx context.Context, + session *auth.Session, + spaceRef string, + filter types.ListQueryFilter, +) ([]*types.Pipeline, int64, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, 0, fmt.Errorf("failed to find parent space: %w", err) + } + + err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionPipelineView, false) + if err != nil { + return nil, 0, fmt.Errorf("could not authorize: %w", err) + } + + var count int64 + var pipelines []*types.Pipeline + + err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { + count, err = c.pipelineStore.Count(ctx, space.ID, filter) + if err != nil { + return fmt.Errorf("failed to count child executions: %w", err) + } + + pipelines, err = c.pipelineStore.List(ctx, space.ID, filter) + if err != nil { + return fmt.Errorf("failed to count child executions: %w", err) + } + return + }, dbtx.TxDefaultReadOnly) + if err != nil { + return pipelines, count, fmt.Errorf("failed to list pipelines: %w", err) + } + + return pipelines, count, nil +} diff --git a/internal/api/controller/space/list_secrets.go b/internal/api/controller/space/list_secrets.go new file mode 100644 index 000000000..1c091d639 --- /dev/null +++ b/internal/api/controller/space/list_secrets.go @@ -0,0 +1,54 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. +package space + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/store/database/dbtx" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// ListSecrets lists the secrets in a space. +func (c *Controller) ListSecrets( + ctx context.Context, + session *auth.Session, + spaceRef string, + filter types.ListQueryFilter, +) ([]*types.Secret, int64, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, 0, fmt.Errorf("failed to find parent space: %w", err) + } + + err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSecretView, false) + if err != nil { + return nil, 0, fmt.Errorf("could not authorize: %w", err) + } + + var count int64 + var secrets []*types.Secret + + err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { + count, err = c.secretStore.Count(ctx, space.ID, filter) + if err != nil { + return fmt.Errorf("failed to count child executions: %w", err) + } + + secrets, err = c.secretStore.List(ctx, space.ID, filter) + if err != nil { + return fmt.Errorf("failed to list child executions: %w", err) + } + return + }, dbtx.TxDefaultReadOnly) + if err != nil { + return secrets, count, fmt.Errorf("failed to list secrets: %w", err) + } + + return secrets, count, nil +} diff --git a/internal/api/controller/space/wire.go b/internal/api/controller/space/wire.go index d8b9cbbba..9b126de43 100644 --- a/internal/api/controller/space/wire.go +++ b/internal/api/controller/space/wire.go @@ -21,12 +21,11 @@ var WireSet = wire.NewSet( ) func ProvideController(db *sqlx.DB, urlProvider *url.Provider, uidCheck check.PathUID, authorizer authz.Authorizer, - pathStore store.PathStore, spaceStore store.SpaceStore, repoStore store.RepoStore, - principalStore store.PrincipalStore, repoCtrl *repo.Controller, - membershipStore store.MembershipStore, + pathStore store.PathStore, pipelineStore store.PipelineStore, secretStore store.SecretStore, + spaceStore store.SpaceStore, repoStore store.RepoStore, principalStore store.PrincipalStore, + repoCtrl *repo.Controller, membershipStore store.MembershipStore, ) *Controller { return NewController(db, urlProvider, uidCheck, authorizer, - pathStore, spaceStore, repoStore, - principalStore, repoCtrl, - membershipStore) + pathStore, pipelineStore, secretStore, spaceStore, repoStore, + principalStore, repoCtrl, membershipStore) } diff --git a/internal/api/handler/execution/create.go b/internal/api/handler/execution/create.go new file mode 100644 index 000000000..97366148e --- /dev/null +++ b/internal/api/handler/execution/create.go @@ -0,0 +1,47 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package execution + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/execution" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +func HandleCreate(executionCtrl *execution.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + pipelineRef, err := request.GetPipelineRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + in := new(execution.CreateInput) + err = json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + execution, err := executionCtrl.Create(ctx, session, spaceRef, pipelineUID, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusCreated, execution) + } +} diff --git a/internal/api/handler/execution/delete.go b/internal/api/handler/execution/delete.go new file mode 100644 index 000000000..77ee63e2f --- /dev/null +++ b/internal/api/handler/execution/delete.go @@ -0,0 +1,44 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package execution + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/execution" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +func HandleDelete(executionCtrl *execution.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + pipelineRef, err := request.GetPipelineRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) + if err != nil { + render.TranslatedUserError(w, err) + return + } + n, err := request.GetExecutionNumberFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + err = executionCtrl.Delete(ctx, session, spaceRef, pipelineUID, n) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.DeleteSuccessful(w) + } +} diff --git a/internal/api/handler/execution/find.go b/internal/api/handler/execution/find.go new file mode 100644 index 000000000..9a4bc4c49 --- /dev/null +++ b/internal/api/handler/execution/find.go @@ -0,0 +1,44 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package execution + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/execution" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +func HandleFind(executionCtrl *execution.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + pipelineRef, err := request.GetPipelineRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + n, err := request.GetExecutionNumberFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + execution, err := executionCtrl.Find(ctx, session, spaceRef, pipelineUID, n) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, execution) + } +} diff --git a/internal/api/handler/execution/list.go b/internal/api/handler/execution/list.go new file mode 100644 index 000000000..eb590f239 --- /dev/null +++ b/internal/api/handler/execution/list.go @@ -0,0 +1,42 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package execution + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/execution" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +func HandleList(executionCtrl *execution.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + pipelineRef, err := request.GetPipelineRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + pagination := request.ParsePaginationFromRequest(r) + + repos, totalCount, err := executionCtrl.List(ctx, session, spaceRef, pipelineUID, pagination) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.Pagination(r, w, pagination.Page, pagination.Size, int(totalCount)) + render.JSON(w, http.StatusOK, repos) + } +} diff --git a/internal/api/handler/execution/update.go b/internal/api/handler/execution/update.go new file mode 100644 index 000000000..14fdf18f9 --- /dev/null +++ b/internal/api/handler/execution/update.go @@ -0,0 +1,53 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package execution + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/execution" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +func HandleUpdate(executionCtrl *execution.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + in := new(execution.UpdateInput) + err := json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + pipelineRef, err := request.GetPipelineRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) + if err != nil { + render.TranslatedUserError(w, err) + return + } + n, err := request.GetExecutionNumberFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + pipeline, err := executionCtrl.Update(ctx, session, spaceRef, pipelineUID, n, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, pipeline) + } +} diff --git a/internal/api/handler/pipeline/create.go b/internal/api/handler/pipeline/create.go new file mode 100644 index 000000000..df1f49a9c --- /dev/null +++ b/internal/api/handler/pipeline/create.go @@ -0,0 +1,36 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pipeline + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/pipeline" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +func HandleCreate(pipelineCtrl *pipeline.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + in := new(pipeline.CreateInput) + err := json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + pipeline, err := pipelineCtrl.Create(ctx, session, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusCreated, pipeline) + } +} diff --git a/internal/api/handler/pipeline/delete.go b/internal/api/handler/pipeline/delete.go new file mode 100644 index 000000000..1679df7a1 --- /dev/null +++ b/internal/api/handler/pipeline/delete.go @@ -0,0 +1,39 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pipeline + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/pipeline" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +func HandleDelete(pipelineCtrl *pipeline.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + pipelineRef, err := request.GetPipelineRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + err = pipelineCtrl.Delete(ctx, session, spaceRef, pipelineUID) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.DeleteSuccessful(w) + } +} diff --git a/internal/api/handler/pipeline/find.go b/internal/api/handler/pipeline/find.go new file mode 100644 index 000000000..74ee6c77d --- /dev/null +++ b/internal/api/handler/pipeline/find.go @@ -0,0 +1,39 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pipeline + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/pipeline" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +func HandleFind(pipelineCtrl *pipeline.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + pipelineRef, err := request.GetPipelineRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + pipeline, err := pipelineCtrl.Find(ctx, session, spaceRef, pipelineUID) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, pipeline) + } +} diff --git a/internal/api/handler/pipeline/update.go b/internal/api/handler/pipeline/update.go new file mode 100644 index 000000000..631ea4a77 --- /dev/null +++ b/internal/api/handler/pipeline/update.go @@ -0,0 +1,48 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pipeline + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/pipeline" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +func HandleUpdate(pipelineCtrl *pipeline.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + in := new(pipeline.UpdateInput) + err := json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + pipelineRef, err := request.GetPipelineRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + pipeline, err := pipelineCtrl.Update(ctx, session, spaceRef, pipelineUID, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, pipeline) + } +} diff --git a/internal/api/handler/repo/get_content.go b/internal/api/handler/repo/content_get.go similarity index 93% rename from internal/api/handler/repo/get_content.go rename to internal/api/handler/repo/content_get.go index 71f7e3e68..c2cd8e17f 100644 --- a/internal/api/handler/repo/get_content.go +++ b/internal/api/handler/repo/content_get.go @@ -12,9 +12,7 @@ import ( "github.com/harness/gitness/internal/api/request" ) -/* - * Writes json-encoded content information to the http response body. - */ +// HandleGetContent handles the get content HTTP API. func HandleGetContent(repoCtrl *repo.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() diff --git a/internal/api/handler/repo/content_paths_details.go b/internal/api/handler/repo/content_paths_details.go new file mode 100644 index 000000000..995c9f3c3 --- /dev/null +++ b/internal/api/handler/repo/content_paths_details.go @@ -0,0 +1,44 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package repo + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/repo" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +// HandlePathsDetails handles get file or directory details HTTP API. +func HandlePathsDetails(repoCtrl *repo.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + repoRef, err := request.GetRepoRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + gitRef := request.GetGitRefFromQueryOrDefault(r, "") + + var in repo.PathsDetailsInput + err = json.NewDecoder(r.Body).Decode(&in) + if err != nil { + render.BadRequestf(w, "Invalid request body: %s.", err) + return + } + + resp, err := repoCtrl.PathsDetails(ctx, session, repoRef, gitRef, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, resp) + } +} diff --git a/internal/api/handler/repo/diff.go b/internal/api/handler/repo/diff.go index d8c6ac11b..23581781e 100644 --- a/internal/api/handler/repo/diff.go +++ b/internal/api/handler/repo/diff.go @@ -43,3 +43,26 @@ func HandleDiff(repoCtrl *repo.Controller) http.HandlerFunc { render.JSONArrayDynamic(ctx, w, stream) } } + +// HandleDiffStats how diff statistics of two commits, branches or tags. +func HandleDiffStats(repoCtrl *repo.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + repoRef, err := request.GetRepoRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + path := request.GetOptionalRemainderFromPath(r) + + output, err := repoCtrl.DiffStats(ctx, session, repoRef, path) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, output) + } +} diff --git a/internal/api/handler/repo/list_commits.go b/internal/api/handler/repo/list_commits.go index 3b0e5dd16..f970efaa5 100644 --- a/internal/api/handler/repo/list_commits.go +++ b/internal/api/handler/repo/list_commits.go @@ -10,7 +10,6 @@ import ( "github.com/harness/gitness/internal/api/controller/repo" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" - "github.com/harness/gitness/types" ) /* @@ -34,20 +33,15 @@ func HandleListCommits(repoCtrl *repo.Controller) http.HandlerFunc { return } - commits, renameDetails, err := repoCtrl.ListCommits(ctx, session, repoRef, gitRef, filter) + list, err := repoCtrl.ListCommits(ctx, session, repoRef, gitRef, filter) if err != nil { render.TranslatedUserError(w, err) return } - commitsResponse := types.ListCommitResponse{ - Commits: commits, - RenameDetails: renameDetails, - } - // TODO: get last page indicator explicitly - current check is wrong in case len % limit == 0 - isLastPage := len(commits) < filter.Limit + isLastPage := len(list.Commits) < filter.Limit render.PaginationNoTotal(r, w, filter.Page, filter.Limit, isLastPage) - render.JSON(w, http.StatusOK, commitsResponse) + render.JSON(w, http.StatusOK, list) } } diff --git a/internal/api/handler/secret/create.go b/internal/api/handler/secret/create.go new file mode 100644 index 000000000..f0e2ac143 --- /dev/null +++ b/internal/api/handler/secret/create.go @@ -0,0 +1,37 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package secret + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/secret" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +// HandleCreate returns a http.HandlerFunc that creates a new secretsitory. +func HandleCreate(secretCtrl *secret.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + in := new(secret.CreateInput) + err := json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + secret, err := secretCtrl.Create(ctx, session, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusCreated, secret.CopyWithoutData()) + } +} diff --git a/internal/api/handler/secret/delete.go b/internal/api/handler/secret/delete.go new file mode 100644 index 000000000..968a9d171 --- /dev/null +++ b/internal/api/handler/secret/delete.go @@ -0,0 +1,39 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package secret + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/secret" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +func HandleDelete(secretCtrl *secret.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + secretRef, err := request.GetSecretRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, secretUID, err := paths.DisectLeaf(secretRef) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + err = secretCtrl.Delete(ctx, session, spaceRef, secretUID) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.DeleteSuccessful(w) + } +} diff --git a/internal/api/handler/secret/find.go b/internal/api/handler/secret/find.go new file mode 100644 index 000000000..e77890a71 --- /dev/null +++ b/internal/api/handler/secret/find.go @@ -0,0 +1,39 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package secret + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/secret" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +// HandleFind finds a secret from the database. +func HandleFind(secretCtrl *secret.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + secretRef, err := request.GetSecretRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, secretUID, err := paths.DisectLeaf(secretRef) + if err != nil { + render.TranslatedUserError(w, err) + } + + secret, err := secretCtrl.Find(ctx, session, spaceRef, secretUID) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, secret.CopyWithoutData()) + } +} diff --git a/internal/api/handler/secret/update.go b/internal/api/handler/secret/update.go new file mode 100644 index 000000000..618319265 --- /dev/null +++ b/internal/api/handler/secret/update.go @@ -0,0 +1,47 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package secret + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/secret" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +func HandleUpdate(secretCtrl *secret.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + in := new(secret.UpdateInput) + err := json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + secretRef, err := request.GetSecretRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, secretUID, err := paths.DisectLeaf(secretRef) + if err != nil { + render.TranslatedUserError(w, err) + } + + secret, err := secretCtrl.Update(ctx, session, spaceRef, secretUID, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, secret.CopyWithoutData()) + } +} diff --git a/internal/api/handler/space/list_pipelines.go b/internal/api/handler/space/list_pipelines.go new file mode 100644 index 000000000..30aba9b4b --- /dev/null +++ b/internal/api/handler/space/list_pipelines.go @@ -0,0 +1,35 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package space + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/space" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +func HandleListPipelines(spaceCtrl *space.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + spaceRef, err := request.GetSpaceRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + filter := request.ParseListQueryFilterFromRequest(r) + repos, totalCount, err := spaceCtrl.ListPipelines(ctx, session, spaceRef, filter) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) + render.JSON(w, http.StatusOK, repos) + } +} diff --git a/internal/api/handler/space/list_secrets.go b/internal/api/handler/space/list_secrets.go new file mode 100644 index 000000000..40c0002aa --- /dev/null +++ b/internal/api/handler/space/list_secrets.go @@ -0,0 +1,42 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package space + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/space" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/types" +) + +func HandleListSecrets(spaceCtrl *space.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + spaceRef, err := request.GetSpaceRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + filter := request.ParseListQueryFilterFromRequest(r) + ret, totalCount, err := spaceCtrl.ListSecrets(ctx, session, spaceRef, filter) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + // Strip out data in the returned value + secrets := []types.Secret{} + for _, s := range ret { + secrets = append(secrets, *s.CopyWithoutData()) + } + + render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) + render.JSON(w, http.StatusOK, secrets) + } +} diff --git a/internal/api/openapi/openapi.go b/internal/api/openapi/openapi.go index 2e55f2e1d..5ea384d0e 100644 --- a/internal/api/openapi/openapi.go +++ b/internal/api/openapi/openapi.go @@ -41,6 +41,8 @@ func Generate() *openapi3.Spec { buildPrincipals(&reflector) spaceOperations(&reflector) repoOperations(&reflector) + pipelineOperations(&reflector) + secretOperations(&reflector) resourceOperations(&reflector) pullReqOperations(&reflector) webhookOperations(&reflector) diff --git a/internal/api/openapi/pipeline.go b/internal/api/openapi/pipeline.go new file mode 100644 index 000000000..533e29a09 --- /dev/null +++ b/internal/api/openapi/pipeline.go @@ -0,0 +1,162 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package openapi + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/execution" + "github.com/harness/gitness/internal/api/controller/pipeline" + "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/types" + + "github.com/swaggest/openapi-go/openapi3" +) + +type pipelineRequest struct { + Ref string `path:"pipeline_ref"` +} + +type executionRequest struct { + pipelineRequest + Number string `path:"execution_number"` +} + +type createExecutionRequest struct { + pipelineRequest + execution.CreateInput +} + +type createPipelineRequest struct { + pipeline.CreateInput +} + +type getExecutionRequest struct { + executionRequest +} + +type getPipelineRequest struct { + pipelineRequest +} + +type updateExecutionRequest struct { + executionRequest + execution.UpdateInput +} + +type updatePipelineRequest struct { + pipelineRequest + pipeline.UpdateInput +} + +func pipelineOperations(reflector *openapi3.Reflector) { + opCreate := openapi3.Operation{} + opCreate.WithTags("pipeline") + opCreate.WithMapOfAnything(map[string]interface{}{"operationId": "createPipeline"}) + _ = reflector.SetRequest(&opCreate, new(createPipelineRequest), http.MethodPost) + _ = reflector.SetJSONResponse(&opCreate, new(types.Pipeline), http.StatusCreated) + _ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusBadRequest) + _ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusForbidden) + _ = reflector.Spec.AddOperation(http.MethodPost, "/pipelines", opCreate) + + opFind := openapi3.Operation{} + opFind.WithTags("pipeline") + opFind.WithMapOfAnything(map[string]interface{}{"operationId": "findPipeline"}) + _ = reflector.SetRequest(&opFind, new(getPipelineRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&opFind, new(types.Pipeline), http.StatusOK) + _ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodGet, "/pipelines/{pipeline_ref}", opFind) + + opDelete := openapi3.Operation{} + opDelete.WithTags("pipeline") + opDelete.WithMapOfAnything(map[string]interface{}{"operationId": "deletePipeline"}) + _ = reflector.SetRequest(&opDelete, new(getPipelineRequest), http.MethodDelete) + _ = reflector.SetJSONResponse(&opDelete, nil, http.StatusNoContent) + _ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodDelete, "/pipelines/{pipeline_ref}", opDelete) + + opUpdate := openapi3.Operation{} + opUpdate.WithTags("pipeline") + opUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "updatePipeline"}) + _ = reflector.SetRequest(&opUpdate, new(updatePipelineRequest), http.MethodPatch) + _ = reflector.SetJSONResponse(&opUpdate, new(types.Pipeline), http.StatusOK) + _ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusBadRequest) + _ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodPatch, + "/pipelines/{pipeline_ref}", opUpdate) + + executionCreate := openapi3.Operation{} + executionCreate.WithTags("pipeline") + executionCreate.WithMapOfAnything(map[string]interface{}{"operationId": "createExecution"}) + _ = reflector.SetRequest(&executionCreate, new(createExecutionRequest), http.MethodPost) + _ = reflector.SetJSONResponse(&executionCreate, new(types.Execution), http.StatusCreated) + _ = reflector.SetJSONResponse(&executionCreate, new(usererror.Error), http.StatusBadRequest) + _ = reflector.SetJSONResponse(&executionCreate, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&executionCreate, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&executionCreate, new(usererror.Error), http.StatusForbidden) + _ = reflector.Spec.AddOperation(http.MethodPost, + "/pipelines/{pipeline_ref}/executions", executionCreate) + + executionFind := openapi3.Operation{} + executionFind.WithTags("pipeline") + executionFind.WithMapOfAnything(map[string]interface{}{"operationId": "findExecution"}) + _ = reflector.SetRequest(&executionFind, new(getExecutionRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&executionFind, new(types.Execution), http.StatusOK) + _ = reflector.SetJSONResponse(&executionFind, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&executionFind, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&executionFind, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&executionFind, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodGet, + "/pipelines/{pipeline_ref}/executions/{execution_number}", executionFind) + + executionDelete := openapi3.Operation{} + executionDelete.WithTags("pipeline") + executionDelete.WithMapOfAnything(map[string]interface{}{"operationId": "deleteExecution"}) + _ = reflector.SetRequest(&executionDelete, new(getExecutionRequest), http.MethodDelete) + _ = reflector.SetJSONResponse(&executionDelete, nil, http.StatusNoContent) + _ = reflector.SetJSONResponse(&executionDelete, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&executionDelete, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&executionDelete, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&executionDelete, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodDelete, + "/pipelines/{pipeline_ref}/executions/{execution_number}", executionDelete) + + executionUpdate := openapi3.Operation{} + executionUpdate.WithTags("pipeline") + executionUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "updateExecution"}) + _ = reflector.SetRequest(&executionUpdate, new(updateExecutionRequest), http.MethodPatch) + _ = reflector.SetJSONResponse(&executionUpdate, new(types.Execution), http.StatusOK) + _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusBadRequest) + _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodPatch, + "/pipelines/{pipeline_ref}/executions/{execution_number}", executionUpdate) + + executionList := openapi3.Operation{} + executionList.WithTags("pipeline") + executionList.WithMapOfAnything(map[string]interface{}{"operationId": "listExecutions"}) + executionList.WithParameters(queryParameterPage, queryParameterLimit) + _ = reflector.SetRequest(&executionList, new(pipelineRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&executionList, []types.Execution{}, http.StatusOK) + _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodGet, + "/pipelines/{pipeline_ref}/executions", executionList) +} diff --git a/internal/api/openapi/repo.go b/internal/api/openapi/repo.go index 7c44a4fe0..8fb613eb9 100644 --- a/internal/api/openapi/repo.go +++ b/internal/api/openapi/repo.go @@ -57,6 +57,11 @@ type getContentRequest struct { Path string `path:"path"` } +type pathsDetailsRequest struct { + repoRequest + repo.PathsDetailsInput +} + type getBlameRequest struct { repoRequest Path string `path:"path"` @@ -481,6 +486,18 @@ func repoOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&opGetContent, new(usererror.Error), http.StatusNotFound) _ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/content/{path}", opGetContent) + opPathDetails := openapi3.Operation{} + opPathDetails.WithTags("repository") + opPathDetails.WithMapOfAnything(map[string]interface{}{"operationId": "pathDetails"}) + opPathDetails.WithParameters(queryParameterGitRef) + _ = reflector.SetRequest(&opPathDetails, new(pathsDetailsRequest), http.MethodPost) + _ = reflector.SetJSONResponse(&opPathDetails, new(repo.PathsDetailsOutput), http.StatusOK) + _ = reflector.SetJSONResponse(&opPathDetails, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opPathDetails, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opPathDetails, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opPathDetails, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodPost, "/repos/{repo_ref}/path-details", opPathDetails) + opGetRaw := openapi3.Operation{} opGetRaw.WithTags("repository") opGetRaw.WithMapOfAnything(map[string]interface{}{"operationId": "getRaw"}) @@ -644,13 +661,23 @@ func repoOperations(reflector *openapi3.Reflector) { opDiff.WithTags("repository") opDiff.WithMapOfAnything(map[string]interface{}{"operationId": "rawDiff"}) _ = reflector.SetRequest(&opDiff, new(getRawDiffRequest), http.MethodGet) - _ = reflector.SetJSONResponse(&opDiff, new(types.DiffStats), http.StatusOK) _ = reflector.SetStringResponse(&opDiff, http.StatusOK, "text/plain") + _ = reflector.SetJSONResponse(&opDiff, []gitrpc.FileDiff{}, http.StatusOK) _ = reflector.SetJSONResponse(&opDiff, new(usererror.Error), http.StatusInternalServerError) _ = reflector.SetJSONResponse(&opDiff, new(usererror.Error), http.StatusUnauthorized) _ = reflector.SetJSONResponse(&opDiff, new(usererror.Error), http.StatusForbidden) _ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/diff/{range}", opDiff) + opDiffStats := openapi3.Operation{} + opDiffStats.WithTags("repository") + opDiffStats.WithMapOfAnything(map[string]interface{}{"operationId": "diffStats"}) + _ = reflector.SetRequest(&opDiffStats, new(getRawDiffRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&opDiffStats, new(types.DiffStats), http.StatusOK) + _ = reflector.SetJSONResponse(&opDiffStats, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opDiffStats, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opDiffStats, new(usererror.Error), http.StatusForbidden) + _ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/diff-stats/{range}", opDiffStats) + opMergeCheck := openapi3.Operation{} opMergeCheck.WithTags("repository") opMergeCheck.WithMapOfAnything(map[string]interface{}{"operationId": "mergeCheck"}) diff --git a/internal/api/openapi/secret.go b/internal/api/openapi/secret.go new file mode 100644 index 000000000..f12fb77ea --- /dev/null +++ b/internal/api/openapi/secret.go @@ -0,0 +1,79 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package openapi + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/secret" + "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/types" + + "github.com/swaggest/openapi-go/openapi3" +) + +type createSecretRequest struct { + secret.CreateInput +} + +type secretRequest struct { + Ref string `path:"secret_ref"` +} + +type getSecretRequest struct { + secretRequest +} + +type updateSecretRequest struct { + secretRequest + secret.UpdateInput +} + +func secretOperations(reflector *openapi3.Reflector) { + opCreate := openapi3.Operation{} + opCreate.WithTags("secret") + opCreate.WithMapOfAnything(map[string]interface{}{"operationId": "createSecret"}) + _ = reflector.SetRequest(&opCreate, new(createSecretRequest), http.MethodPost) + _ = reflector.SetJSONResponse(&opCreate, new(types.Secret), http.StatusCreated) + _ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusBadRequest) + _ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusForbidden) + _ = reflector.Spec.AddOperation(http.MethodPost, "/secrets", opCreate) + + opFind := openapi3.Operation{} + opFind.WithTags("secret") + opFind.WithMapOfAnything(map[string]interface{}{"operationId": "findSecret"}) + _ = reflector.SetRequest(&opFind, new(getSecretRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&opFind, new(types.Secret), http.StatusOK) + _ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodGet, "/secrets/{secret_ref}", opFind) + + opDelete := openapi3.Operation{} + opDelete.WithTags("secret") + opDelete.WithMapOfAnything(map[string]interface{}{"operationId": "deleteSecret"}) + _ = reflector.SetRequest(&opDelete, new(getSecretRequest), http.MethodDelete) + _ = reflector.SetJSONResponse(&opDelete, nil, http.StatusNoContent) + _ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodDelete, "/secrets/{secret_ref}", opDelete) + + opUpdate := openapi3.Operation{} + opUpdate.WithTags("secret") + opUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "updateSecret"}) + _ = reflector.SetRequest(&opUpdate, new(updateSecretRequest), http.MethodPatch) + _ = reflector.SetJSONResponse(&opUpdate, new(types.Secret), http.StatusOK) + _ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusBadRequest) + _ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodPatch, "/secrets/{secret_ref}", opUpdate) +} diff --git a/internal/api/openapi/space.go b/internal/api/openapi/space.go index 04569762c..b6e5a0573 100644 --- a/internal/api/openapi/space.go +++ b/internal/api/openapi/space.go @@ -230,6 +230,30 @@ func spaceOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&opRepos, new(usererror.Error), http.StatusNotFound) _ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/repos", opRepos) + opPipelines := openapi3.Operation{} + opPipelines.WithTags("space") + opPipelines.WithMapOfAnything(map[string]interface{}{"operationId": "listPipelines"}) + opPipelines.WithParameters(queryParameterQueryRepo, queryParameterPage, queryParameterLimit) + _ = reflector.SetRequest(&opPipelines, new(spaceRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&opPipelines, []types.Pipeline{}, http.StatusOK) + _ = reflector.SetJSONResponse(&opPipelines, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opPipelines, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opPipelines, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opPipelines, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/pipelines", opPipelines) + + opSecrets := openapi3.Operation{} + opSecrets.WithTags("space") + opSecrets.WithMapOfAnything(map[string]interface{}{"operationId": "listSecrets"}) + opSecrets.WithParameters(queryParameterQueryRepo, queryParameterPage, queryParameterLimit) + _ = reflector.SetRequest(&opSecrets, new(spaceRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&opSecrets, []types.Secret{}, http.StatusOK) + _ = reflector.SetJSONResponse(&opSecrets, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opSecrets, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opSecrets, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opSecrets, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/secrets", opSecrets) + opServiceAccounts := openapi3.Operation{} opServiceAccounts.WithTags("space") opServiceAccounts.WithMapOfAnything(map[string]interface{}{"operationId": "listServiceAccounts"}) diff --git a/internal/api/request/pipeline.go b/internal/api/request/pipeline.go new file mode 100644 index 000000000..8806102a3 --- /dev/null +++ b/internal/api/request/pipeline.go @@ -0,0 +1,29 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package request + +import ( + "net/http" + "net/url" +) + +const ( + PathParamPipelineRef = "pipeline_ref" + PathParamExecutionNumber = "execution_number" +) + +func GetPipelineRefFromPath(r *http.Request) (string, error) { + rawRef, err := PathParamOrError(r, PathParamPipelineRef) + if err != nil { + return "", err + } + + // paths are unescaped + return url.PathUnescape(rawRef) +} + +func GetExecutionNumberFromPath(r *http.Request) (int64, error) { + return PathParamAsPositiveInt64(r, PathParamExecutionNumber) +} diff --git a/internal/api/request/pullreq.go b/internal/api/request/pullreq.go index 19a743ae3..9f471b633 100644 --- a/internal/api/request/pullreq.go +++ b/internal/api/request/pullreq.go @@ -102,7 +102,7 @@ func ParsePullReqActivityFilter(r *http.Request) (*types.PullReqActivityFilter, // parsePullReqActivityKinds extracts the pull request activity kinds from the url. func parsePullReqActivityKinds(r *http.Request) []enum.PullReqActivityKind { - strKinds := r.Form[QueryParamKind] + strKinds := r.URL.Query()[QueryParamKind] m := make(map[enum.PullReqActivityKind]struct{}) // use map to eliminate duplicates for _, s := range strKinds { if kind, ok := enum.PullReqActivityKind(s).Sanitize(); ok { @@ -124,7 +124,7 @@ func parsePullReqActivityKinds(r *http.Request) []enum.PullReqActivityKind { // parsePullReqActivityTypes extracts the pull request activity types from the url. func parsePullReqActivityTypes(r *http.Request) []enum.PullReqActivityType { - strType := r.Form[QueryParamType] + strType := r.URL.Query()[QueryParamType] m := make(map[enum.PullReqActivityType]struct{}) // use map to eliminate duplicates for _, s := range strType { if t, ok := enum.PullReqActivityType(s).Sanitize(); ok { diff --git a/internal/api/request/secret.go b/internal/api/request/secret.go new file mode 100644 index 000000000..d039a6f5b --- /dev/null +++ b/internal/api/request/secret.go @@ -0,0 +1,24 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package request + +import ( + "net/http" + "net/url" +) + +const ( + PathParamSecretRef = "secret_ref" +) + +func GetSecretRefFromPath(r *http.Request) (string, error) { + rawRef, err := PathParamOrError(r, PathParamSecretRef) + if err != nil { + return "", err + } + + // paths are unescaped + return url.PathUnescape(rawRef) +} diff --git a/internal/api/request/util.go b/internal/api/request/util.go index 8e3b47459..cc0be0a28 100644 --- a/internal/api/request/util.go +++ b/internal/api/request/util.go @@ -9,6 +9,7 @@ import ( "strconv" "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" "github.com/go-chi/chi" @@ -17,11 +18,10 @@ import ( const ( PathParamRemainder = "*" - QueryParamSort = "sort" - QueryParamOrder = "order" - QueryParamQuery = "query" - QueryParamCreatedBy = "created_by" + QueryParamSort = "sort" + QueryParamOrder = "order" + QueryParamQuery = "query" QueryParamState = "state" QueryParamKind = "kind" @@ -204,3 +204,19 @@ func ParseOrder(r *http.Request) enum.Order { func ParseSort(r *http.Request) string { return r.URL.Query().Get(QueryParamSort) } + +// ParsePaginationFromRequest parses pagination related info from the url. +func ParsePaginationFromRequest(r *http.Request) types.Pagination { + return types.Pagination{ + Page: ParsePage(r), + Size: ParseLimit(r), + } +} + +// ParseListQueryFilterFromRequest parses pagination and query related info from the url. +func ParseListQueryFilterFromRequest(r *http.Request) types.ListQueryFilter { + return types.ListQueryFilter{ + Query: ParseQuery(r), + Pagination: ParsePaginationFromRequest(r), + } +} diff --git a/internal/auth/authz/membership.go b/internal/auth/authz/membership.go index 0bcfc4f19..65bd4e398 100644 --- a/internal/auth/authz/membership.go +++ b/internal/auth/authz/membership.go @@ -63,6 +63,12 @@ func (a *MembershipAuthorizer) Check( case enum.ResourceTypeServiceAccount: spaceRef = scope.SpacePath + case enum.ResourceTypePipeline: + spaceRef = scope.SpacePath + + case enum.ResourceTypeSecret: + spaceRef = scope.SpacePath + case enum.ResourceTypeUser: // a user is allowed to view / edit themselves if resource.Name == session.Principal.UID && diff --git a/internal/router/api.go b/internal/router/api.go index 40e3c1823..711fd19a8 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -10,21 +10,27 @@ import ( "github.com/harness/gitness/githook" "github.com/harness/gitness/internal/api/controller/check" + "github.com/harness/gitness/internal/api/controller/execution" controllergithook "github.com/harness/gitness/internal/api/controller/githook" + "github.com/harness/gitness/internal/api/controller/pipeline" "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" "github.com/harness/gitness/internal/api/controller/repo" + "github.com/harness/gitness/internal/api/controller/secret" "github.com/harness/gitness/internal/api/controller/serviceaccount" "github.com/harness/gitness/internal/api/controller/space" "github.com/harness/gitness/internal/api/controller/user" "github.com/harness/gitness/internal/api/controller/webhook" "github.com/harness/gitness/internal/api/handler/account" handlercheck "github.com/harness/gitness/internal/api/handler/check" + handlerexecution "github.com/harness/gitness/internal/api/handler/execution" handlergithook "github.com/harness/gitness/internal/api/handler/githook" + handlerpipeline "github.com/harness/gitness/internal/api/handler/pipeline" handlerprincipal "github.com/harness/gitness/internal/api/handler/principal" handlerpullreq "github.com/harness/gitness/internal/api/handler/pullreq" handlerrepo "github.com/harness/gitness/internal/api/handler/repo" "github.com/harness/gitness/internal/api/handler/resource" + handlersecret "github.com/harness/gitness/internal/api/handler/secret" handlerserviceaccount "github.com/harness/gitness/internal/api/handler/serviceaccount" handlerspace "github.com/harness/gitness/internal/api/handler/space" "github.com/harness/gitness/internal/api/handler/system" @@ -54,7 +60,7 @@ type APIHandler interface { var ( // terminatedPathPrefixesAPI is the list of prefixes that will require resolving terminated paths. - terminatedPathPrefixesAPI = []string{"/v1/spaces/", "/v1/repos/"} + terminatedPathPrefixesAPI = []string{"/v1/spaces/", "/v1/repos/", "/v1/pipelines/", "/v1/secrets/"} ) // NewAPIHandler returns a new APIHandler. @@ -62,7 +68,10 @@ func NewAPIHandler( config *types.Config, authenticator authn.Authenticator, repoCtrl *repo.Controller, + executionCtrl *execution.Controller, spaceCtrl *space.Controller, + pipelineCtrl *pipeline.Controller, + secretCtrl *secret.Controller, pullreqCtrl *pullreq.Controller, webhookCtrl *webhook.Controller, githookCtrl *controllergithook.Controller, @@ -92,7 +101,7 @@ func NewAPIHandler( r.Use(middlewareauthn.Attempt(authenticator, authn.SourceRouterAPI)) r.Route("/v1", func(r chi.Router) { - setupRoutesV1(r, repoCtrl, spaceCtrl, pullreqCtrl, webhookCtrl, githookCtrl, + setupRoutesV1(r, repoCtrl, executionCtrl, pipelineCtrl, secretCtrl, spaceCtrl, pullreqCtrl, webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl, checkCtrl) }) @@ -115,6 +124,9 @@ func corsHandler(config *types.Config) func(http.Handler) http.Handler { func setupRoutesV1(r chi.Router, repoCtrl *repo.Controller, + executionCtrl *execution.Controller, + pipelineCtrl *pipeline.Controller, + secretCtrl *secret.Controller, spaceCtrl *space.Controller, pullreqCtrl *pullreq.Controller, webhookCtrl *webhook.Controller, @@ -126,6 +138,8 @@ func setupRoutesV1(r chi.Router, ) { setupSpaces(r, spaceCtrl) setupRepos(r, repoCtrl, pullreqCtrl, webhookCtrl, checkCtrl) + setupPipelines(r, pipelineCtrl, executionCtrl) + setupSecrets(r, secretCtrl) setupUser(r, userCtrl) setupServiceAccounts(r, saCtrl) setupPrincipals(r, principalCtrl) @@ -151,6 +165,8 @@ func setupSpaces(r chi.Router, spaceCtrl *space.Controller) { r.Get("/spaces", handlerspace.HandleListSpaces(spaceCtrl)) r.Get("/repos", handlerspace.HandleListRepos(spaceCtrl)) r.Get("/service-accounts", handlerspace.HandleListServiceAccounts(spaceCtrl)) + r.Get("/pipelines", handlerspace.HandleListPipelines(spaceCtrl)) + r.Get("/secrets", handlerspace.HandleListSecrets(spaceCtrl)) // Child collections r.Route("/paths", func(r chi.Router) { @@ -200,6 +216,8 @@ func setupRepos(r chi.Router, r.Get("/*", handlerrepo.HandleGetContent(repoCtrl)) }) + r.Post("/path-details", handlerrepo.HandlePathsDetails(repoCtrl)) + r.Route("/blame", func(r chi.Router) { r.Get("/*", handlerrepo.HandleBlame(repoCtrl)) }) @@ -253,6 +271,9 @@ func setupRepos(r chi.Router, r.Route("/diff", func(r chi.Router) { r.Get("/*", handlerrepo.HandleDiff(repoCtrl)) }) + r.Route("/diff-stats", func(r chi.Router) { + r.Get("/*", handlerrepo.HandleDiffStats(repoCtrl)) + }) r.Route("/merge-check", func(r chi.Router) { r.Post("/*", handlerrepo.HandleMergeCheck(repoCtrl)) }) @@ -266,6 +287,43 @@ func setupRepos(r chi.Router, }) } +func setupPipelines(r chi.Router, pipelineCtrl *pipeline.Controller, executionCtrl *execution.Controller) { + r.Route("/pipelines", func(r chi.Router) { + // Create takes path and parentId via body, not uri + r.Post("/", handlerpipeline.HandleCreate(pipelineCtrl)) + r.Route(fmt.Sprintf("/{%s}", request.PathParamPipelineRef), func(r chi.Router) { + r.Get("/", handlerpipeline.HandleFind(pipelineCtrl)) + r.Patch("/", handlerpipeline.HandleUpdate(pipelineCtrl)) + r.Delete("/", handlerpipeline.HandleDelete(pipelineCtrl)) + setupExecutions(r, pipelineCtrl, executionCtrl) + }) + }) +} + +func setupSecrets(r chi.Router, secretCtrl *secret.Controller) { + r.Route("/secrets", func(r chi.Router) { + // Create takes path and parentId via body, not uri + r.Post("/", handlersecret.HandleCreate(secretCtrl)) + r.Route(fmt.Sprintf("/{%s}", request.PathParamSecretRef), func(r chi.Router) { + r.Get("/", handlersecret.HandleFind(secretCtrl)) + r.Patch("/", handlersecret.HandleUpdate(secretCtrl)) + r.Delete("/", handlersecret.HandleDelete(secretCtrl)) + }) + }) +} + +func setupExecutions(r chi.Router, pipelineCtrl *pipeline.Controller, executionCtrl *execution.Controller) { + r.Route("/executions", func(r chi.Router) { + r.Get("/", handlerexecution.HandleList(executionCtrl)) + r.Post("/", handlerexecution.HandleCreate(executionCtrl)) + r.Route(fmt.Sprintf("/{%s}", request.PathParamExecutionNumber), func(r chi.Router) { + r.Get("/", handlerexecution.HandleFind(executionCtrl)) + r.Patch("/", handlerexecution.HandleUpdate(executionCtrl)) + r.Delete("/", handlerexecution.HandleDelete(executionCtrl)) + }) + }) +} + func setupInternal(r chi.Router, githookCtrl *controllergithook.Controller) { r.Route("/internal", func(r chi.Router) { SetupGitHooks(r, githookCtrl) diff --git a/internal/router/wire.go b/internal/router/wire.go index 3118ca790..d01d8e6d3 100644 --- a/internal/router/wire.go +++ b/internal/router/wire.go @@ -7,10 +7,13 @@ package router import ( "github.com/harness/gitness/gitrpc" "github.com/harness/gitness/internal/api/controller/check" + "github.com/harness/gitness/internal/api/controller/execution" "github.com/harness/gitness/internal/api/controller/githook" + "github.com/harness/gitness/internal/api/controller/pipeline" "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" "github.com/harness/gitness/internal/api/controller/repo" + "github.com/harness/gitness/internal/api/controller/secret" "github.com/harness/gitness/internal/api/controller/serviceaccount" "github.com/harness/gitness/internal/api/controller/space" "github.com/harness/gitness/internal/api/controller/user" @@ -57,7 +60,10 @@ func ProvideAPIHandler( config *types.Config, authenticator authn.Authenticator, repoCtrl *repo.Controller, + executionCtrl *execution.Controller, spaceCtrl *space.Controller, + pipelineCtrl *pipeline.Controller, + secretCtrl *secret.Controller, pullreqCtrl *pullreq.Controller, webhookCtrl *webhook.Controller, githookCtrl *githook.Controller, @@ -66,8 +72,8 @@ func ProvideAPIHandler( principalCtrl principal.Controller, checkCtrl *check.Controller, ) APIHandler { - return NewAPIHandler(config, authenticator, repoCtrl, spaceCtrl, pullreqCtrl, - webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl, checkCtrl) + return NewAPIHandler(config, authenticator, repoCtrl, executionCtrl, spaceCtrl, pipelineCtrl, secretCtrl, + pullreqCtrl, webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl, checkCtrl) } func ProvideWebHandler(config *types.Config) WebHandler { diff --git a/internal/services/codecomments/migrator.go b/internal/services/codecomments/migrator.go index 577901c40..cef57912f 100644 --- a/internal/services/codecomments/migrator.go +++ b/internal/services/codecomments/migrator.go @@ -91,7 +91,7 @@ func (migrator *Migrator) migrate( return } - commitMap := mapCodeComments(comments, getSHA) + commitMap, initialValuesMap := mapCodeComments(comments, getSHA) for commentSHA, fileMap := range commitMap { // get all hunk headers for the diff between the SHA that's stored in the comment and the new SHA. @@ -144,7 +144,7 @@ func (migrator *Migrator) migrate( continue } - // Handle new files - shouldn't happen because code comments should exist for a non-existing file. + // Handle new files - shouldn't happen because no code comments should exist for a non-existing file. if _, isAdded := file.FileHeader.Extensions[gitrpcenum.DiffExtHeaderNewFileMode]; isAdded { for _, codeComment := range codeComments { codeComment.Outdated = true @@ -154,9 +154,18 @@ func (migrator *Migrator) migrate( for _, hunk := range file.HunkHeaders { for _, cc := range codeComments { + if cc.Outdated { + continue + } + ccStart, ccEnd := getCommentStartEnd(cc) outdated, moveDelta := processCodeComment(ccStart, ccEnd, hunk) - cc.Outdated = outdated + if outdated { + cc.CodeCommentFields = initialValuesMap[cc.ID] // revert the CC to the original values + cc.Outdated = true + continue + } + updateCommentLine(cc, moveDelta) } } @@ -178,8 +187,10 @@ func (migrator *Migrator) migrate( func mapCodeComments( comments []*types.CodeComment, extractSHA func(*types.CodeComment) string, -) map[string]map[string][]*types.CodeComment { +) (map[string]map[string][]*types.CodeComment, map[int64]types.CodeCommentFields) { commitMap := map[string]map[string][]*types.CodeComment{} + originalComments := make(map[int64]types.CodeCommentFields, len(comments)) + for _, comment := range comments { commitSHA := extractSHA(comment) @@ -193,16 +204,22 @@ func mapCodeComments( fileMap[comment.Path] = fileComments commitMap[commitSHA] = fileMap + + originalComments[comment.ID] = comment.CodeCommentFields } - return commitMap + return commitMap, originalComments } func processCodeComment(ccStart, ccEnd int, h gitrpc.HunkHeader) (outdated bool, moveDelta int) { - // it's outdated if code is changed (old code) inside the code comment or - // if there are added lines inside the code comment, don't care about how many (NewSpan). - outdated = (h.OldSpan > 0 && ccEnd >= h.OldLine && ccStart <= h.OldLine+h.OldSpan-1) || - (h.NewSpan > 0 && h.NewLine > ccStart && h.NewLine <= ccEnd) + // A code comment is marked as outdated if: + // * The code lines covered by the code comment are changed + // (the range given by the OldLine/OldSpan overlaps the code comment's code range) + // * There are new lines inside the line range covered by the code comment, don't care about how many + // (the NewLine is between the CC start and CC end; the value of the NewSpan is unimportant). + outdated = + (h.OldSpan > 0 && ccEnd >= h.OldLine && ccStart <= h.OldLine+h.OldSpan-1) || // code comment's code is changed + (h.NewSpan > 0 && h.NewLine > ccStart && h.NewLine <= ccEnd) // lines are added inside the code comment if outdated { return // outdated comments aren't moved diff --git a/internal/store/cache/path.go b/internal/store/cache/path.go index 8a47d7ef3..41b725ce5 100644 --- a/internal/store/cache/path.go +++ b/internal/store/cache/path.go @@ -13,38 +13,24 @@ import ( "github.com/harness/gitness/types" ) -// pathCacheEntry is used to return the proper transformed value as identifier when storing a path in cache. -type pathCacheEntry struct { - inner *types.Path - valueUnique string -} - -func (e *pathCacheEntry) Identifier() string { - return e.valueUnique -} - // pathCacheGetter is used to hook a PathStore as source of a pathCache. // IMPORTANT: It assumes that the pathCache already transformed the key. type pathCacheGetter struct { pathStore store.PathStore } -func (g *pathCacheGetter) Find(ctx context.Context, key string) (*pathCacheEntry, error) { +func (g *pathCacheGetter) Find(ctx context.Context, key string) (*types.Path, error) { path, err := g.pathStore.FindValue(ctx, key) if err != nil { return nil, err } - return &pathCacheEntry{ - inner: path, - // key is already transformed - pathCache transforms the path before calling the inner cache. - valueUnique: key, - }, nil + return path, nil } // pathCache is a decorator of a Cache required to handle path transformations. type pathCache struct { - inner cache.Cache[string, *pathCacheEntry] + inner cache.Cache[string, *types.Path] pathTransformation store.PathTransformation } @@ -54,12 +40,12 @@ func (c *pathCache) Get(ctx context.Context, key string) (*types.Path, error) { return nil, fmt.Errorf("failed to transform path: %w", err) } - cacheEntry, err := c.inner.Get(ctx, uniqueKey) + path, err := c.inner.Get(ctx, uniqueKey) if err != nil { return nil, err } - return cacheEntry.inner, nil + return path, nil } func (c *pathCache) Stats() (int64, int64) { diff --git a/internal/store/cache/wire.go b/internal/store/cache/wire.go index 58231d950..da4eaf6f6 100644 --- a/internal/store/cache/wire.go +++ b/internal/store/cache/wire.go @@ -29,7 +29,7 @@ func ProvidePrincipalInfoCache(getter store.PrincipalInfoView) store.PrincipalIn // ProvidePathCache provides a cache for storing routing paths and their types.Path objects. func ProvidePathCache(pathStore store.PathStore, pathTransformation store.PathTransformation) store.PathCache { return &pathCache{ - inner: cache.New[string, *pathCacheEntry]( + inner: cache.New[string, *types.Path]( &pathCacheGetter{ pathStore: pathStore, }, diff --git a/internal/store/database.go b/internal/store/database.go index 5dc84cf30..f716f10ab 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -439,4 +439,90 @@ type ( // Delete removes a required status checks for a repo. Delete(ctx context.Context, repoID, reqCheckID int64) error } + PipelineStore interface { + // Find returns a pipeline given a pipeline ID from the datastore. + Find(ctx context.Context, id int64) (*types.Pipeline, error) + + // FindByUID returns a pipeline with a given UID in a space + FindByUID(ctx context.Context, id int64, uid string) (*types.Pipeline, error) + + // Create creates a new pipeline in the datastore. + Create(ctx context.Context, pipeline *types.Pipeline) error + + // Update tries to update a pipeline in the datastore + Update(ctx context.Context, pipeline *types.Pipeline) error + + // List lists the pipelines present in a parent space ID in the datastore. + List(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) ([]*types.Pipeline, error) + + // UpdateOptLock updates the pipeline using the optimistic locking mechanism. + UpdateOptLock(ctx context.Context, pipeline *types.Pipeline, + mutateFn func(pipeline *types.Pipeline) error) (*types.Pipeline, error) + + // Delete deletes a pipeline ID from the datastore. + Delete(ctx context.Context, id int64) error + + // Count the number of pipelines in a space matching the given filter. + Count(ctx context.Context, spaceID int64, filter types.ListQueryFilter) (int64, error) + + // DeleteByUID deletes a pipeline with a given UID in a space + DeleteByUID(ctx context.Context, spaceID int64, uid string) error + + // IncrementSeqNum increments the sequence number of the pipeline + IncrementSeqNum(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) + } + + SecretStore interface { + // Find returns a secret given an ID + Find(ctx context.Context, id int64) (*types.Secret, error) + + // FindByUID returns a secret given a space ID and a UID + FindByUID(ctx context.Context, spaceID int64, uid string) (*types.Secret, error) + + // Create creates a new secret + Create(ctx context.Context, secret *types.Secret) error + + // Count the number of secrets in a space matching the given filter. + Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error) + + // UpdateOptLock updates the secret using the optimistic locking mechanism. + UpdateOptLock(ctx context.Context, secret *types.Secret, + mutateFn func(secret *types.Secret) error) (*types.Secret, error) + + // Update tries to update a secret. + Update(ctx context.Context, secret *types.Secret) error + + // Delete deletes a secret given an ID. + Delete(ctx context.Context, id int64) error + + // DeleteByUID deletes a secret given a space ID and a uid + DeleteByUID(ctx context.Context, spaceID int64, uid string) error + + // List lists the secrets in a given space + List(ctx context.Context, spaceID int64, filter types.ListQueryFilter) ([]*types.Secret, error) + } + + ExecutionStore interface { + // Find returns a execution given a pipeline and an execution number + Find(ctx context.Context, pipelineID int64, num int64) (*types.Execution, error) + + // Create creates a new execution in the datastore. + Create(ctx context.Context, execution *types.Execution) error + + // Update tries to update an execution. + Update(ctx context.Context, execution *types.Execution) error + + // UpdateOptLock updates the execution using the optimistic locking mechanism. + UpdateOptLock(ctx context.Context, exectuion *types.Execution, + mutateFn func(execution *types.Execution) error) (*types.Execution, error) + + // List lists the executions for a given pipeline ID + List(ctx context.Context, pipelineID int64, pagination types.Pagination) ([]*types.Execution, error) + + // Delete deletes an execution given a pipeline ID and an execution number + Delete(ctx context.Context, pipelineID int64, num int64) error + + // Count the number of executions in a space + Count(ctx context.Context, parentID int64) (int64, error) + } ) diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go new file mode 100644 index 000000000..187d33805 --- /dev/null +++ b/internal/store/database/execution.go @@ -0,0 +1,315 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package database + +import ( + "context" + "fmt" + "time" + + "github.com/harness/gitness/internal/store" + gitness_store "github.com/harness/gitness/store" + "github.com/harness/gitness/store/database" + "github.com/harness/gitness/store/database/dbtx" + "github.com/harness/gitness/types" + + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" +) + +var _ store.ExecutionStore = (*executionStore)(nil) + +// NewExecutionStore returns a new ExecutionStore. +func NewExecutionStore(db *sqlx.DB) *executionStore { + return &executionStore{ + db: db, + } +} + +type executionStore struct { + db *sqlx.DB +} + +const ( + executionColumns = ` + execution_id + ,execution_pipeline_id + ,execution_repo_id + ,execution_trigger + ,execution_number + ,execution_parent + ,execution_status + ,execution_error + ,execution_event + ,execution_action + ,execution_link + ,execution_timestamp + ,execution_title + ,execution_message + ,execution_before + ,execution_after + ,execution_ref + ,execution_source_repo + ,execution_source + ,execution_target + ,execution_author + ,execution_author_name + ,execution_author_email + ,execution_author_avatar + ,execution_sender + ,execution_params + ,execution_cron + ,execution_deploy + ,execution_deploy_id + ,execution_debug + ,execution_started + ,execution_finished + ,execution_created + ,execution_updated + ,execution_version + ` +) + +// Find returns an execution given a pipeline ID and an execution number. +func (s *executionStore) Find(ctx context.Context, pipelineID int64, executionNum int64) (*types.Execution, error) { + const findQueryStmt = ` + SELECT` + executionColumns + ` + FROM executions + WHERE execution_pipeline_id = $1 AND execution_number = $2` + db := dbtx.GetAccessor(ctx, s.db) + + dst := new(types.Execution) + if err := db.GetContext(ctx, dst, findQueryStmt, pipelineID, executionNum); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find execution") + } + return dst, nil +} + +// Create creates a new execution in the datastore. +func (s *executionStore) Create(ctx context.Context, execution *types.Execution) error { + const executionInsertStmt = ` + INSERT INTO executions ( + execution_pipeline_id + ,execution_repo_id + ,execution_trigger + ,execution_number + ,execution_parent + ,execution_status + ,execution_error + ,execution_event + ,execution_action + ,execution_link + ,execution_timestamp + ,execution_title + ,execution_message + ,execution_before + ,execution_after + ,execution_ref + ,execution_source_repo + ,execution_source + ,execution_target + ,execution_author + ,execution_author_name + ,execution_author_email + ,execution_author_avatar + ,execution_sender + ,execution_params + ,execution_cron + ,execution_deploy + ,execution_deploy_id + ,execution_debug + ,execution_started + ,execution_finished + ,execution_created + ,execution_updated + ,execution_version + ) VALUES ( + :execution_pipeline_id + ,:execution_repo_id + ,:execution_trigger + ,:execution_number + ,:execution_parent + ,:execution_status + ,:execution_error + ,:execution_event + ,:execution_action + ,:execution_link + ,:execution_timestamp + ,:execution_title + ,:execution_message + ,:execution_before + ,:execution_after + ,:execution_ref + ,:execution_source_repo + ,:execution_source + ,:execution_target + ,:execution_author + ,:execution_author_name + ,:execution_author_email + ,:execution_author_avatar + ,:execution_sender + ,:execution_params + ,:execution_cron + ,:execution_deploy + ,:execution_deploy_id + ,:execution_debug + ,:execution_started + ,:execution_finished + ,:execution_created + ,:execution_updated + ,:execution_version + ) RETURNING execution_id` + db := dbtx.GetAccessor(ctx, s.db) + + query, arg, err := db.BindNamed(executionInsertStmt, execution) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to bind execution object") + } + + if err = db.QueryRowContext(ctx, query, arg...).Scan(&execution.ID); err != nil { + return database.ProcessSQLErrorf(err, "Execution query failed") + } + + return nil +} + +// Update tries to update an execution in the datastore with optimistic locking. +func (s *executionStore) Update(ctx context.Context, e *types.Execution) error { + const executionUpdateStmt = ` + UPDATE executions + SET + ,execution_status = :execution_status + ,execution_error = :execution_error + ,execution_event = :execution_event + ,execution_started = :execution_started + ,execution_finished = :execution_finished + ,execution_updated = :execution_updated + ,execution_version = :execution_version + WHERE execution_id = :execution_id AND execution_version = :execution_version - 1` + updatedAt := time.Now() + + execution := *e + + execution.Version++ + execution.Updated = updatedAt.UnixMilli() + + db := dbtx.GetAccessor(ctx, s.db) + + query, arg, err := db.BindNamed(executionUpdateStmt, execution) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to bind execution object") + } + + result, err := db.ExecContext(ctx, query, arg...) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to update execution") + } + + count, err := result.RowsAffected() + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to get number of updated rows") + } + + if count == 0 { + return gitness_store.ErrVersionConflict + } + + e.Version = execution.Version + e.Updated = execution.Updated + return nil +} + +// UpdateOptLock updates the pipeline using the optimistic locking mechanism. +func (s *executionStore) UpdateOptLock(ctx context.Context, + execution *types.Execution, + mutateFn func(execution *types.Execution) error) (*types.Execution, error) { + for { + dup := *execution + + err := mutateFn(&dup) + if err != nil { + return nil, err + } + + err = s.Update(ctx, &dup) + if err == nil { + return &dup, nil + } + if !errors.Is(err, gitness_store.ErrVersionConflict) { + return nil, err + } + + execution, err = s.Find(ctx, execution.PipelineID, execution.Number) + if err != nil { + return nil, err + } + } +} + +// List lists the executions for a given pipeline ID. +func (s *executionStore) List( + ctx context.Context, + pipelineID int64, + pagination types.Pagination, +) ([]*types.Execution, error) { + stmt := database.Builder. + Select(executionColumns). + From("executions"). + Where("execution_pipeline_id = ?", fmt.Sprint(pipelineID)) + + stmt = stmt.Limit(database.Limit(pagination.Size)) + stmt = stmt.Offset(database.Offset(pagination.Page, pagination.Size)) + + sql, args, err := stmt.ToSql() + if err != nil { + return nil, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + dst := []*types.Execution{} + if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed executing custom list query") + } + + return dst, nil +} + +// Count of executions in a space. +func (s *executionStore) Count(ctx context.Context, pipelineID int64) (int64, error) { + stmt := database.Builder. + Select("count(*)"). + From("executions"). + Where("execution_pipeline_id = ?", pipelineID) + + sql, args, err := stmt.ToSql() + if err != nil { + return 0, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + var count int64 + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) + if err != nil { + return 0, database.ProcessSQLErrorf(err, "Failed executing count query") + } + return count, nil +} + +// Delete deletes an execution given a pipeline ID and an execution number. +func (s *executionStore) Delete(ctx context.Context, pipelineID int64, executionNum int64) error { + const executionDeleteStmt = ` + DELETE FROM executions + WHERE execution_pipeline_id = $1 AND execution_number = $2` + + db := dbtx.GetAccessor(ctx, s.db) + + if _, err := db.ExecContext(ctx, executionDeleteStmt, pipelineID, executionNum); err != nil { + return database.ProcessSQLErrorf(err, "Could not delete execution") + } + + return nil +} diff --git a/internal/store/database/migrate/ci/ci_migrations.sql b/internal/store/database/migrate/ci/ci_migrations.sql new file mode 100644 index 000000000..2b1ed484d --- /dev/null +++ b/internal/store/database/migrate/ci/ci_migrations.sql @@ -0,0 +1,103 @@ +CREATE TABLE IF NOT EXISTS pipelines ( + pipeline_id INTEGER PRIMARY KEY AUTOINCREMENT + ,pipeline_description TEXT NOT NULL + ,pipeline_space_id INTEGER NOT NULL + ,pipeline_uid TEXT NOT NULL + ,pipeline_seq INTEGER NOT NULL DEFAULT 0 + ,pipeline_repo_id INTEGER + ,pipeline_repo_type TEXT NOT NULL + ,pipeline_repo_name TEXT + ,pipeline_default_branch TEXT + ,pipeline_config_path TEXT NOT NULL + ,pipeline_created INTEGER NOT NULL + ,pipeline_updated INTEGER NOT NULL + ,pipeline_version INTEGER NOT NULL + + -- Ensure unique combination of UID and ParentID + ,UNIQUE (pipeline_space_id, pipeline_uid) + + -- Foreign key to spaces table + ,CONSTRAINT fk_pipeline_space_id FOREIGN KEY (pipeline_space_id) + REFERENCES spaces (space_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE + + -- Foreign key to repositories table + ,CONSTRAINT fk_pipelines_repo_id FOREIGN KEY (pipeline_repo_id) + REFERENCES repositories (repo_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS executions ( + execution_id INTEGER PRIMARY KEY AUTOINCREMENT + ,execution_pipeline_id INTEGER NOT NULL + ,execution_repo_id INTEGER + ,execution_trigger TEXT + ,execution_number INTEGER NOT NULL + ,execution_parent INTEGER + ,execution_status TEXT + ,execution_error TEXT + ,execution_event TEXT + ,execution_action TEXT + ,execution_link TEXT + ,execution_timestamp INTEGER + ,execution_title TEXT + ,execution_message TEXT + ,execution_before TEXT + ,execution_after TEXT + ,execution_ref TEXT + ,execution_source_repo TEXT + ,execution_source TEXT + ,execution_target TEXT + ,execution_author TEXT + ,execution_author_name TEXT + ,execution_author_email TEXT + ,execution_author_avatar TEXT + ,execution_sender TEXT + ,execution_params TEXT + ,execution_cron TEXT + ,execution_deploy TEXT + ,execution_deploy_id INTEGER + ,execution_debug BOOLEAN NOT NULL DEFAULT 0 + ,execution_started INTEGER + ,execution_finished INTEGER + ,execution_created INTEGER NOT NULL + ,execution_updated INTEGER NOT NULL + ,execution_version INTEGER NOT NULL + + -- Ensure unique combination of pipeline ID and number + ,UNIQUE (execution_pipeline_id, execution_number) + + -- Foreign key to pipelines table + ,CONSTRAINT fk_executions_pipeline_id FOREIGN KEY (execution_pipeline_id) + REFERENCES pipelines (pipeline_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE + + -- Foreign key to repositories table + ,CONSTRAINT fk_executions_repo_id FOREIGN KEY (execution_repo_id) + REFERENCES repositories (repo_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS secrets ( + secret_id INTEGER PRIMARY KEY AUTOINCREMENT + ,secret_uid TEXT NOT NULL + ,secret_space_id INTEGER NOT NULL + ,secret_description TEXT NOT NULL + ,secret_data BLOB NOT NULL + ,secret_created INTEGER NOT NULL + ,secret_updated INTEGER NOT NULL + ,secret_version INTEGER NOT NULL + + -- Ensure unique combination of space ID and UID + ,UNIQUE (secret_space_id, secret_uid) + + -- Foreign key to spaces table + ,CONSTRAINT fk_secrets_space_id FOREIGN KEY (secret_space_id) + REFERENCES spaces (space_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +); \ No newline at end of file diff --git a/internal/store/database/migrate/postgres/0020_alter_pullreq_source_repo_id_constraint.down.sql b/internal/store/database/migrate/postgres/0020_alter_pullreq_source_repo_id_constraint.down.sql new file mode 100644 index 000000000..6c6e2c61f --- /dev/null +++ b/internal/store/database/migrate/postgres/0020_alter_pullreq_source_repo_id_constraint.down.sql @@ -0,0 +1,8 @@ +ALTER TABLE pullreqs + DROP CONSTRAINT fk_pullreq_source_repo_id; + +ALTER TABLE pullreqs + ADD CONSTRAINT fk_pullreq_source_repo_id FOREIGN KEY (pullreq_source_repo_id) + REFERENCES repositories (repo_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE SET NULL; \ No newline at end of file diff --git a/internal/store/database/migrate/postgres/0020_alter_pullreq_source_repo_id_constraint.up.sql b/internal/store/database/migrate/postgres/0020_alter_pullreq_source_repo_id_constraint.up.sql new file mode 100644 index 000000000..80ea9d550 --- /dev/null +++ b/internal/store/database/migrate/postgres/0020_alter_pullreq_source_repo_id_constraint.up.sql @@ -0,0 +1,8 @@ +ALTER TABLE pullreqs + DROP CONSTRAINT fk_pullreq_source_repo_id; + +ALTER TABLE pullreqs + ADD CONSTRAINT fk_pullreq_source_repo_id FOREIGN KEY (pullreq_source_repo_id) + REFERENCES repositories (repo_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE; \ No newline at end of file diff --git a/internal/store/database/migrate/sqlite/0020_alter_pullreq_source_repo_id_constraint.down.sql b/internal/store/database/migrate/sqlite/0020_alter_pullreq_source_repo_id_constraint.down.sql new file mode 100644 index 000000000..b534b9b56 --- /dev/null +++ b/internal/store/database/migrate/sqlite/0020_alter_pullreq_source_repo_id_constraint.down.sql @@ -0,0 +1,116 @@ +CREATE TABLE pullreqs_new ( + pullreq_id INTEGER PRIMARY KEY AUTOINCREMENT +,pullreq_version INTEGER NOT NULL DEFAULT 0 +,pullreq_created_by INTEGER NOT NULL +,pullreq_created BIGINT NOT NULL +,pullreq_updated BIGINT NOT NULL +,pullreq_edited BIGINT NOT NULL +,pullreq_number INTEGER NOT NULL +,pullreq_state TEXT NOT NULL +,pullreq_is_draft TEXT NOT NULL DEFAULT FALSE +,pullreq_comment_count INTEGER NOT NULL DEFAULT 0 +,pullreq_title TEXT NOT NULL +,pullreq_description TEXT NOT NULL +,pullreq_source_repo_id INTEGER NOT NULL +,pullreq_source_branch TEXT NOT NULL +,pullreq_source_sha TEXT NOT NULL +,pullreq_target_repo_id INTEGER NOT NULL +,pullreq_target_branch TEXT NOT NULL +,pullreq_activity_seq INTEGER DEFAULT 0 +,pullreq_merged_by INTEGER +,pullreq_merged BIGINT +,pullreq_merge_method TEXT +,pullreq_merge_check_status TEXT NOT NULL +,pullreq_merge_target_sha TEXT +,pullreq_merge_sha TEXT +,pullreq_merge_conflicts TEXT +,pullreq_merge_base_sha TEXT NOT NULL DEFAULT '' +,pullreq_unresolved_count INTEGER NOT NULL DEFAULT 0 +,CONSTRAINT fk_pullreq_created_by FOREIGN KEY (pullreq_created_by) + REFERENCES principals + ON UPDATE NO ACTION + ON DELETE NO ACTION +,CONSTRAINT fk_pullreq_source_repo_id FOREIGN KEY (pullreq_source_repo_id) + REFERENCES repositories + ON UPDATE NO ACTION + ON DELETE SET NULL +,CONSTRAINT fk_pullreq_target_repo_id FOREIGN KEY (pullreq_target_repo_id) + REFERENCES repositories + ON UPDATE NO ACTION + ON DELETE CASCADE +,CONSTRAINT fk_pullreq_merged_by FOREIGN KEY (pullreq_merged_by) + REFERENCES principals + ON UPDATE NO ACTION + ON DELETE NO ACTION +); + +INSERT INTO pullreqs_new( + pullreq_id +,pullreq_version +,pullreq_created_by +,pullreq_created +,pullreq_updated +,pullreq_edited +,pullreq_number +,pullreq_state +,pullreq_is_draft +,pullreq_comment_count +,pullreq_title +,pullreq_description +,pullreq_source_repo_id +,pullreq_source_branch +,pullreq_source_sha +,pullreq_target_repo_id +,pullreq_target_branch +,pullreq_activity_seq +,pullreq_merged_by +,pullreq_merged +,pullreq_merge_method +,pullreq_merge_check_status +,pullreq_merge_target_sha +,pullreq_merge_sha +,pullreq_merge_conflicts +,pullreq_merge_base_sha +,pullreq_unresolved_count +) +SELECT + pullreq_id +,pullreq_version +,pullreq_created_by +,pullreq_created +,pullreq_updated +,pullreq_edited +,pullreq_number +,pullreq_state +,pullreq_is_draft +,pullreq_comment_count +,pullreq_title +,pullreq_description +,pullreq_source_repo_id +,pullreq_source_branch +,pullreq_source_sha +,pullreq_target_repo_id +,pullreq_target_branch +,pullreq_activity_seq +,pullreq_merged_by +,pullreq_merged +,pullreq_merge_method +,pullreq_merge_check_status +,pullreq_merge_target_sha +,pullreq_merge_sha +,pullreq_merge_conflicts +,pullreq_merge_base_sha +,pullreq_unresolved_count +FROM pullreqs; + +DROP TABLE pullreqs; + +ALTER TABLE pullreqs_new + RENAME TO pullreqs; + +CREATE UNIQUE INDEX pullreqs_source_repo_branch_target_repo_branch + ON pullreqs (pullreq_source_repo_id, pullreq_source_branch, pullreq_target_repo_id, pullreq_target_branch) + WHERE pullreq_state = 'open'; + +CREATE UNIQUE INDEX pullreqs_target_repo_id_number + ON pullreqs (pullreq_target_repo_id, pullreq_number); diff --git a/internal/store/database/migrate/sqlite/0020_alter_pullreq_source_repo_id_constraint.up.sql b/internal/store/database/migrate/sqlite/0020_alter_pullreq_source_repo_id_constraint.up.sql new file mode 100644 index 000000000..b3dbc9e65 --- /dev/null +++ b/internal/store/database/migrate/sqlite/0020_alter_pullreq_source_repo_id_constraint.up.sql @@ -0,0 +1,116 @@ +CREATE TABLE pullreqs_new ( + pullreq_id INTEGER PRIMARY KEY AUTOINCREMENT +,pullreq_version INTEGER NOT NULL DEFAULT 0 +,pullreq_created_by INTEGER NOT NULL +,pullreq_created BIGINT NOT NULL +,pullreq_updated BIGINT NOT NULL +,pullreq_edited BIGINT NOT NULL +,pullreq_number INTEGER NOT NULL +,pullreq_state TEXT NOT NULL +,pullreq_is_draft TEXT NOT NULL DEFAULT FALSE +,pullreq_comment_count INTEGER NOT NULL DEFAULT 0 +,pullreq_title TEXT NOT NULL +,pullreq_description TEXT NOT NULL +,pullreq_source_repo_id INTEGER NOT NULL +,pullreq_source_branch TEXT NOT NULL +,pullreq_source_sha TEXT NOT NULL +,pullreq_target_repo_id INTEGER NOT NULL +,pullreq_target_branch TEXT NOT NULL +,pullreq_activity_seq INTEGER DEFAULT 0 +,pullreq_merged_by INTEGER +,pullreq_merged BIGINT +,pullreq_merge_method TEXT +,pullreq_merge_check_status TEXT NOT NULL +,pullreq_merge_target_sha TEXT +,pullreq_merge_sha TEXT +,pullreq_merge_conflicts TEXT +,pullreq_merge_base_sha TEXT NOT NULL DEFAULT '' +,pullreq_unresolved_count INTEGER NOT NULL DEFAULT 0 +,CONSTRAINT fk_pullreq_created_by FOREIGN KEY (pullreq_created_by) + REFERENCES principals + ON UPDATE NO ACTION + ON DELETE NO ACTION +,CONSTRAINT fk_pullreq_source_repo_id FOREIGN KEY (pullreq_source_repo_id) + REFERENCES repositories + ON UPDATE NO ACTION + ON DELETE CASCADE +,CONSTRAINT fk_pullreq_target_repo_id FOREIGN KEY (pullreq_target_repo_id) + REFERENCES repositories + ON UPDATE NO ACTION + ON DELETE CASCADE +,CONSTRAINT fk_pullreq_merged_by FOREIGN KEY (pullreq_merged_by) + REFERENCES principals + ON UPDATE NO ACTION + ON DELETE NO ACTION +); + +INSERT INTO pullreqs_new( + pullreq_id +,pullreq_version +,pullreq_created_by +,pullreq_created +,pullreq_updated +,pullreq_edited +,pullreq_number +,pullreq_state +,pullreq_is_draft +,pullreq_comment_count +,pullreq_title +,pullreq_description +,pullreq_source_repo_id +,pullreq_source_branch +,pullreq_source_sha +,pullreq_target_repo_id +,pullreq_target_branch +,pullreq_activity_seq +,pullreq_merged_by +,pullreq_merged +,pullreq_merge_method +,pullreq_merge_check_status +,pullreq_merge_target_sha +,pullreq_merge_sha +,pullreq_merge_conflicts +,pullreq_merge_base_sha +,pullreq_unresolved_count +) +SELECT + pullreq_id +,pullreq_version +,pullreq_created_by +,pullreq_created +,pullreq_updated +,pullreq_edited +,pullreq_number +,pullreq_state +,pullreq_is_draft +,pullreq_comment_count +,pullreq_title +,pullreq_description +,pullreq_source_repo_id +,pullreq_source_branch +,pullreq_source_sha +,pullreq_target_repo_id +,pullreq_target_branch +,pullreq_activity_seq +,pullreq_merged_by +,pullreq_merged +,pullreq_merge_method +,pullreq_merge_check_status +,pullreq_merge_target_sha +,pullreq_merge_sha +,pullreq_merge_conflicts +,pullreq_merge_base_sha +,pullreq_unresolved_count +FROM pullreqs; + +DROP TABLE pullreqs; + +ALTER TABLE pullreqs_new + RENAME TO pullreqs; + +CREATE UNIQUE INDEX pullreqs_source_repo_branch_target_repo_branch + ON pullreqs (pullreq_source_repo_id, pullreq_source_branch, pullreq_target_repo_id, pullreq_target_branch) + WHERE pullreq_state = 'open'; + +CREATE UNIQUE INDEX pullreqs_target_repo_id_number + ON pullreqs (pullreq_target_repo_id, pullreq_number); diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go new file mode 100644 index 000000000..9ac2aaaf7 --- /dev/null +++ b/internal/store/database/pipeline.go @@ -0,0 +1,307 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package database + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/harness/gitness/internal/store" + gitness_store "github.com/harness/gitness/store" + "github.com/harness/gitness/store/database" + "github.com/harness/gitness/store/database/dbtx" + "github.com/harness/gitness/types" + + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" +) + +var _ store.PipelineStore = (*pipelineStore)(nil) + +const ( + pipelineQueryBase = ` + SELECT` + + pipelineColumns + ` + FROM pipelines` + + pipelineColumns = ` + pipeline_id + ,pipeline_description + ,pipeline_space_id + ,pipeline_uid + ,pipeline_seq + ,pipeline_repo_id + ,pipeline_repo_type + ,pipeline_repo_name + ,pipeline_default_branch + ,pipeline_config_path + ,pipeline_created + ,pipeline_updated + ,pipeline_version + ` +) + +// NewPipelineStore returns a new PipelineStore. +func NewPipelineStore(db *sqlx.DB) *pipelineStore { + return &pipelineStore{ + db: db, + } +} + +type pipelineStore struct { + db *sqlx.DB +} + +// Find returns a pipeline given a pipeline ID. +func (s *pipelineStore) Find(ctx context.Context, id int64) (*types.Pipeline, error) { + const findQueryStmt = pipelineQueryBase + ` + WHERE pipeline_id = $1` + db := dbtx.GetAccessor(ctx, s.db) + + dst := new(types.Pipeline) + if err := db.GetContext(ctx, dst, findQueryStmt, id); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find pipeline") + } + return dst, nil +} + +// FindByUID returns a pipeline in a given space with a given UID. +func (s *pipelineStore) FindByUID(ctx context.Context, spaceID int64, uid string) (*types.Pipeline, error) { + const findQueryStmt = pipelineQueryBase + ` + WHERE pipeline_space_id = $1 AND pipeline_uid = $2` + db := dbtx.GetAccessor(ctx, s.db) + + dst := new(types.Pipeline) + if err := db.GetContext(ctx, dst, findQueryStmt, spaceID, uid); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find pipeline") + } + return dst, nil +} + +// Create creates a pipeline. +func (s *pipelineStore) Create(ctx context.Context, pipeline *types.Pipeline) error { + const pipelineInsertStmt = ` + INSERT INTO pipelines ( + pipeline_description + ,pipeline_space_id + ,pipeline_uid + ,pipeline_seq + ,pipeline_repo_id + ,pipeline_repo_type + ,pipeline_repo_name + ,pipeline_default_branch + ,pipeline_config_path + ,pipeline_created + ,pipeline_updated + ,pipeline_version + ) VALUES ( + :pipeline_description, + :pipeline_space_id, + :pipeline_uid, + :pipeline_seq, + :pipeline_repo_id, + :pipeline_repo_type, + :pipeline_repo_name, + :pipeline_default_branch, + :pipeline_config_path, + :pipeline_created, + :pipeline_updated, + :pipeline_version + ) RETURNING pipeline_id` + db := dbtx.GetAccessor(ctx, s.db) + + query, arg, err := db.BindNamed(pipelineInsertStmt, pipeline) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to bind pipeline object") + } + + if err = db.QueryRowContext(ctx, query, arg...).Scan(&pipeline.ID); err != nil { + return database.ProcessSQLErrorf(err, "Pipeline query failed") + } + + return nil +} + +// Update updates a pipeline. +func (s *pipelineStore) Update(ctx context.Context, p *types.Pipeline) error { + const pipelineUpdateStmt = ` + UPDATE pipelines + SET + pipeline_description = :pipeline_description, + pipeline_uid = :pipeline_uid, + pipeline_seq = :pipeline_seq, + pipeline_default_branch = :pipeline_default_branch, + pipeline_config_path = :pipeline_config_path, + pipeline_updated = :pipeline_updated, + pipeline_version = :pipeline_version + WHERE pipeline_id = :pipeline_id AND pipeline_version = :pipeline_version - 1` + updatedAt := time.Now() + pipeline := *p + + pipeline.Version++ + pipeline.Updated = updatedAt.UnixMilli() + + db := dbtx.GetAccessor(ctx, s.db) + + query, arg, err := db.BindNamed(pipelineUpdateStmt, pipeline) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to bind pipeline object") + } + + result, err := db.ExecContext(ctx, query, arg...) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to update pipeline") + } + + count, err := result.RowsAffected() + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to get number of updated rows") + } + + if count == 0 { + return gitness_store.ErrVersionConflict + } + + p.Updated = pipeline.Updated + p.Version = pipeline.Version + return nil +} + +// List lists all the pipelines present in a space. +func (s *pipelineStore) List( + ctx context.Context, + parentID int64, + filter types.ListQueryFilter, +) ([]*types.Pipeline, error) { + stmt := database.Builder. + Select(pipelineColumns). + From("pipelines"). + Where("pipeline_space_id = ?", fmt.Sprint(parentID)) + + if filter.Query != "" { + stmt = stmt.Where("LOWER(pipeline_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query))) + } + + stmt = stmt.Limit(database.Limit(filter.Size)) + stmt = stmt.Offset(database.Offset(filter.Page, filter.Size)) + + sql, args, err := stmt.ToSql() + if err != nil { + return nil, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + dst := []*types.Pipeline{} + if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed executing custom list query") + } + + return dst, nil +} + +// UpdateOptLock updates the pipeline using the optimistic locking mechanism. +func (s *pipelineStore) UpdateOptLock(ctx context.Context, + pipeline *types.Pipeline, + mutateFn func(pipeline *types.Pipeline) error) (*types.Pipeline, error) { + for { + dup := *pipeline + + err := mutateFn(&dup) + if err != nil { + return nil, err + } + + err = s.Update(ctx, &dup) + if err == nil { + return &dup, nil + } + if !errors.Is(err, gitness_store.ErrVersionConflict) { + return nil, err + } + + pipeline, err = s.Find(ctx, pipeline.ID) + if err != nil { + return nil, err + } + } +} + +// Count of pipelines in a space. +func (s *pipelineStore) Count(ctx context.Context, parentID int64, filter types.ListQueryFilter) (int64, error) { + stmt := database.Builder. + Select("count(*)"). + From("pipelines"). + Where("pipeline_space_id = ?", parentID) + + if filter.Query != "" { + stmt = stmt.Where("pipeline_uid LIKE ?", fmt.Sprintf("%%%s%%", filter.Query)) + } + + sql, args, err := stmt.ToSql() + if err != nil { + return 0, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + var count int64 + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) + if err != nil { + return 0, database.ProcessSQLErrorf(err, "Failed executing count query") + } + return count, nil +} + +// Delete deletes a pipeline given a pipeline ID. +func (s *pipelineStore) Delete(ctx context.Context, id int64) error { + const pipelineDeleteStmt = ` + DELETE FROM pipelines + WHERE pipeline_id = $1` + + db := dbtx.GetAccessor(ctx, s.db) + + if _, err := db.ExecContext(ctx, pipelineDeleteStmt, id); err != nil { + return database.ProcessSQLErrorf(err, "Could not delete pipeline") + } + + return nil +} + +// DeleteByUID deletes a pipeline with a given UID in a space. +func (s *pipelineStore) DeleteByUID(ctx context.Context, spaceID int64, uid string) error { + const pipelineDeleteStmt = ` + DELETE FROM pipelines + WHERE pipeline_space_id = $1 AND pipeline_uid = $2` + + db := dbtx.GetAccessor(ctx, s.db) + + if _, err := db.ExecContext(ctx, pipelineDeleteStmt, spaceID, uid); err != nil { + return database.ProcessSQLErrorf(err, "Could not delete pipeline") + } + + return nil +} + +// Increment increments the pipeline sequence number. It will keep retrying in case +// of optimistic lock errors. +func (s *pipelineStore) IncrementSeqNum(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) { + for { + var err error + pipeline.Seq++ + err = s.Update(ctx, pipeline) + if err == nil { + return pipeline, nil + } else if !errors.Is(err, gitness_store.ErrVersionConflict) { + return pipeline, errors.Wrap(err, "could not increment pipeline sequence number") + } + pipeline, err = s.Find(ctx, pipeline.ID) + if err != nil { + return nil, errors.Wrap(err, "could not increment pipeline sequence number") + } + } +} diff --git a/internal/store/database/secret.go b/internal/store/database/secret.go new file mode 100644 index 000000000..8ade23d57 --- /dev/null +++ b/internal/store/database/secret.go @@ -0,0 +1,266 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package database + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/harness/gitness/internal/store" + gitness_store "github.com/harness/gitness/store" + "github.com/harness/gitness/store/database" + "github.com/harness/gitness/store/database/dbtx" + "github.com/harness/gitness/types" + + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" +) + +var _ store.SecretStore = (*secretStore)(nil) + +const ( + secretQueryBase = ` + SELECT` + secretColumns + ` + FROM secrets` + + secretColumns = ` + secret_id, + secret_description, + secret_space_id, + secret_uid, + secret_data, + secret_created, + secret_updated, + secret_version + ` +) + +// NewSecretStore returns a new SecretStore. +func NewSecretStore(db *sqlx.DB) *secretStore { + return &secretStore{ + db: db, + } +} + +type secretStore struct { + db *sqlx.DB +} + +// Find returns a secret given a secret ID. +func (s *secretStore) Find(ctx context.Context, id int64) (*types.Secret, error) { + const findQueryStmt = secretQueryBase + ` + WHERE secret_id = $1` + db := dbtx.GetAccessor(ctx, s.db) + + dst := new(types.Secret) + if err := db.GetContext(ctx, dst, findQueryStmt, id); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find secret") + } + return dst, nil +} + +// FindByUID returns a secret in a given space with a given UID. +func (s *secretStore) FindByUID(ctx context.Context, spaceID int64, uid string) (*types.Secret, error) { + const findQueryStmt = secretQueryBase + ` + WHERE secret_space_id = $1 AND secret_uid = $2` + db := dbtx.GetAccessor(ctx, s.db) + + dst := new(types.Secret) + if err := db.GetContext(ctx, dst, findQueryStmt, spaceID, uid); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find secret") + } + return dst, nil +} + +// Create creates a secret. +func (s *secretStore) Create(ctx context.Context, secret *types.Secret) error { + const secretInsertStmt = ` + INSERT INTO secrets ( + secret_description, + secret_space_id, + secret_uid, + secret_data, + secret_created, + secret_updated, + secret_version + ) VALUES ( + :secret_description, + :secret_space_id, + :secret_uid, + :secret_data, + :secret_created, + :secret_updated, + :secret_version + ) RETURNING secret_id` + db := dbtx.GetAccessor(ctx, s.db) + + query, arg, err := db.BindNamed(secretInsertStmt, secret) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to bind secret object") + } + + if err = db.QueryRowContext(ctx, query, arg...).Scan(&secret.ID); err != nil { + return database.ProcessSQLErrorf(err, "secret query failed") + } + + return nil +} + +func (s *secretStore) Update(ctx context.Context, p *types.Secret) error { + const secretUpdateStmt = ` + UPDATE secrets + SET + secret_description = :secret_description, + secret_uid = :secret_uid, + secret_data = :secret_data, + secret_updated = :secret_updated, + secret_version = :secret_version + WHERE secret_id = :secret_id AND secret_version = :secret_version - 1` + updatedAt := time.Now() + secret := *p + + secret.Version++ + secret.Updated = updatedAt.UnixMilli() + + db := dbtx.GetAccessor(ctx, s.db) + + query, arg, err := db.BindNamed(secretUpdateStmt, secret) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to bind secret object") + } + + result, err := db.ExecContext(ctx, query, arg...) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to update secret") + } + + count, err := result.RowsAffected() + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to get number of updated rows") + } + + if count == 0 { + return gitness_store.ErrVersionConflict + } + + p.Version = secret.Version + p.Updated = secret.Updated + return nil +} + +// UpdateOptLock updates the pipeline using the optimistic locking mechanism. +func (s *secretStore) UpdateOptLock(ctx context.Context, + secret *types.Secret, + mutateFn func(secret *types.Secret) error, +) (*types.Secret, error) { + for { + dup := *secret + + err := mutateFn(&dup) + if err != nil { + return nil, err + } + + err = s.Update(ctx, &dup) + if err == nil { + return &dup, nil + } + if !errors.Is(err, gitness_store.ErrVersionConflict) { + return nil, err + } + + secret, err = s.Find(ctx, secret.ID) + if err != nil { + return nil, err + } + } +} + +// List lists all the secrets present in a space. +func (s *secretStore) List(ctx context.Context, parentID int64, filter types.ListQueryFilter) ([]*types.Secret, error) { + stmt := database.Builder. + Select(secretColumns). + From("secrets"). + Where("secret_space_id = ?", fmt.Sprint(parentID)) + + if filter.Query != "" { + stmt = stmt.Where("LOWER(secret_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query))) + } + + stmt = stmt.Limit(database.Limit(filter.Size)) + stmt = stmt.Offset(database.Offset(filter.Page, filter.Size)) + + sql, args, err := stmt.ToSql() + if err != nil { + return nil, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + dst := []*types.Secret{} + if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed executing custom list query") + } + + return dst, nil +} + +// Delete deletes a secret given a secret ID. +func (s *secretStore) Delete(ctx context.Context, id int64) error { + const secretDeleteStmt = ` + DELETE FROM secrets + WHERE secret_id = $1` + + db := dbtx.GetAccessor(ctx, s.db) + + if _, err := db.ExecContext(ctx, secretDeleteStmt, id); err != nil { + return database.ProcessSQLErrorf(err, "Could not delete secret") + } + + return nil +} + +// DeleteByUID deletes a secret with a given UID in a space. +func (s *secretStore) DeleteByUID(ctx context.Context, spaceID int64, uid string) error { + const secretDeleteStmt = ` + DELETE FROM secrets + WHERE secret_space_id = $1 AND secret_uid = $2` + + db := dbtx.GetAccessor(ctx, s.db) + + if _, err := db.ExecContext(ctx, secretDeleteStmt, spaceID, uid); err != nil { + return database.ProcessSQLErrorf(err, "Could not delete secret") + } + + return nil +} + +// Count of secrets in a space. +func (s *secretStore) Count(ctx context.Context, parentID int64, filter types.ListQueryFilter) (int64, error) { + stmt := database.Builder. + Select("count(*)"). + From("secrets"). + Where("secret_space_id = ?", parentID) + + if filter.Query != "" { + stmt = stmt.Where("secret_uid LIKE ?", fmt.Sprintf("%%%s%%", filter.Query)) + } + + sql, args, err := stmt.ToSql() + if err != nil { + return 0, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + var count int64 + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) + if err != nil { + return 0, database.ProcessSQLErrorf(err, "Failed executing count query") + } + return count, nil +} diff --git a/internal/store/database/wire.go b/internal/store/database/wire.go index ff6a66870..d61f7879c 100644 --- a/internal/store/database/wire.go +++ b/internal/store/database/wire.go @@ -23,6 +23,9 @@ var WireSet = wire.NewSet( ProvidePathStore, ProvideSpaceStore, ProvideRepoStore, + ProvideExecutionStore, + ProvidePipelineStore, + ProvideSecretStore, ProvideRepoGitInfoView, ProvideMembershipStore, ProvideTokenStore, @@ -78,6 +81,21 @@ func ProvideRepoStore(db *sqlx.DB, pathCache store.PathCache) store.RepoStore { return NewRepoStore(db, pathCache) } +// ProvidePipelineStore provides a pipeline store. +func ProvidePipelineStore(db *sqlx.DB) store.PipelineStore { + return NewPipelineStore(db) +} + +// ProvideSecretStore provides a secret store. +func ProvideSecretStore(db *sqlx.DB) store.SecretStore { + return NewSecretStore(db) +} + +// ProvideExecutionStore provides an execution store. +func ProvideExecutionStore(db *sqlx.DB) store.ExecutionStore { + return NewExecutionStore(db) +} + // ProvideRepoGitInfoView provides a repo git UID view. func ProvideRepoGitInfoView(db *sqlx.DB) store.RepoGitInfoView { return NewRepoGitInfoView(db) diff --git a/mocks/mock_client.go b/mocks/mock_client.go index b16a6490e..3cb746055 100644 --- a/mocks/mock_client.go +++ b/mocks/mock_client.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" user "github.com/harness/gitness/internal/api/controller/user" types "github.com/harness/gitness/types" + + gomock "github.com/golang/mock/gomock" ) // MockClient is a mock of Client interface. diff --git a/mocks/mock_store.go b/mocks/mock_store.go index 0af0bbc34..9310f3729 100644 --- a/mocks/mock_store.go +++ b/mocks/mock_store.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" types "github.com/harness/gitness/types" enum "github.com/harness/gitness/types/enum" + + gomock "github.com/golang/mock/gomock" ) // MockPrincipalStore is a mock of PrincipalStore interface. diff --git a/store/errors.go b/store/errors.go index 191d5ea3e..b090e0385 100644 --- a/store/errors.go +++ b/store/errors.go @@ -20,4 +20,5 @@ var ( "cyclic depdency") ErrSpaceWithChildsCantBeDeleted = errors.New("the space can't be deleted as it still contains " + "spaces or repos") + ErrPreConditionFailed = errors.New("precondition failed") ) diff --git a/types/check.go b/types/check.go index 23b0d8a6a..825e080d1 100644 --- a/types/check.go +++ b/types/check.go @@ -49,3 +49,7 @@ type ReqCheck struct { AddedBy PrincipalInfo `json:"added_by"` } + +type CheckPayloadText struct { + Details string `json:"details"` +} diff --git a/types/config.go b/types/config.go index ac4af71a1..a94fbcb91 100644 --- a/types/config.go +++ b/types/config.go @@ -52,6 +52,12 @@ type Config struct { DefaultBranch string `envconfig:"GITNESS_GIT_DEFAULTBRANCH" default:"main"` } + // Encrypter defines the parameters for the encrypter + Encrypter struct { + Secret string `envconfig:"GITNESS_ENCRYPTER_SECRET"` // key used for encryption + MixedContent bool `envconfig:"GITNESS_ENCRYPTER_MIXED_CONTENT"` + } + // Server defines the server configuration parameters. Server struct { // HTTP defines the http configuration parameters @@ -128,10 +134,13 @@ type Config struct { } Redis struct { - Endpoint string `envconfig:"GITNESS_REDIS_ENDPOINT" default:"localhost:6379"` - MaxRetries int `envconfig:"GITNESS_REDIS_MAX_RETRIES" default:"3"` - MinIdleConnections int `envconfig:"GITNESS_REDIS_MIN_IDLE_CONNECTIONS" default:"0"` + Endpoint string `envconfig:"GITNESS_REDIS_ENDPOINT" default:"localhost:6379"` + MaxRetries int `envconfig:"GITNESS_REDIS_MAX_RETRIES" default:"3"` + MinIdleConnections int `envconfig:"GITNESS_REDIS_MIN_IDLE_CONNECTIONS" default:"0"` Password string `envconfig:"GITNESS_REDIS_PASSWORD"` + SentinelMode bool `envconfig:"GITNESS_REDIS_USE_SENTINEL" default:"false"` + SentinelMaster string `envconfig:"GITNESS_REDIS_SENTINEL_MASTER"` + SentinelEndpoint string `envconfig:"GITNESS_REDIS_SENTINEL_ENDPOINT"` } Lock struct { diff --git a/types/enum/check.go b/types/enum/check.go index acf190f27..e348d6e9f 100644 --- a/types/enum/check.go +++ b/types/enum/check.go @@ -36,14 +36,18 @@ func (s CheckPayloadKind) Sanitize() (CheckPayloadKind, bool) { return Sanitize(s, GetAllCheckPayloadTypes) } func GetAllCheckPayloadTypes() ([]CheckPayloadKind, CheckPayloadKind) { - return checkPayloadTypes, CheckPayloadKindExternal + return checkPayloadTypes, CheckPayloadKindEmpty } // CheckPayloadKind enumeration. const ( - CheckPayloadKindExternal CheckPayloadKind = "external" + CheckPayloadKindEmpty CheckPayloadKind = "" + CheckPayloadKindRaw CheckPayloadKind = "raw" + CheckPayloadKindMarkdown CheckPayloadKind = "markdown" ) var checkPayloadTypes = sortEnum([]CheckPayloadKind{ - CheckPayloadKindExternal, + CheckPayloadKindEmpty, + CheckPayloadKindRaw, + CheckPayloadKindMarkdown, }) diff --git a/types/enum/membership_role.go b/types/enum/membership_role.go index f7888d2c2..fbe2b9d6a 100644 --- a/types/enum/membership_role.go +++ b/types/enum/membership_role.go @@ -24,10 +24,14 @@ var membershipRoleReaderPermissions = slices.Clip(slices.Insert([]Permission{}, PermissionRepoView, PermissionSpaceView, PermissionServiceAccountView, + PermissionPipelineView, + PermissionSecretView, )) var membershipRoleExecutorPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, PermissionCommitCheckReport, + PermissionPipelineExecute, + PermissionSecretAccess, )) var membershipRoleContributorPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, @@ -47,6 +51,16 @@ var membershipRoleSpaceOwnerPermissions = slices.Clip(slices.Insert(membershipRo PermissionServiceAccountCreate, PermissionServiceAccountEdit, PermissionServiceAccountDelete, + + PermissionPipelineEdit, + PermissionPipelineExecute, + PermissionPipelineDelete, + PermissionPipelineView, + + PermissionSecretAccess, + PermissionSecretDelete, + PermissionSecretEdit, + PermissionSecretView, )) func init() { diff --git a/types/enum/permission.go b/types/enum/permission.go index 6824f035a..fe8d0fc6e 100644 --- a/types/enum/permission.go +++ b/types/enum/permission.go @@ -13,6 +13,8 @@ const ( ResourceTypeUser ResourceType = "USER" ResourceTypeServiceAccount ResourceType = "SERVICEACCOUNT" ResourceTypeService ResourceType = "SERVICE" + ResourceTypePipeline ResourceType = "PIPELINE" + ResourceTypeSecret ResourceType = "SECRET" // ResourceType_Branch ResourceType = "BRANCH" ) @@ -71,6 +73,26 @@ const ( PermissionServiceEditAdmin Permission = "service_editAdmin" ) +const ( + /* + ----- PIPELINE ----- + */ + PermissionPipelineView Permission = "pipeline_view" + PermissionPipelineEdit Permission = "pipeline_edit" + PermissionPipelineDelete Permission = "pipeline_delete" + PermissionPipelineExecute Permission = "pipeline_execute" +) + +const ( + /* + ----- SECRET ----- + */ + PermissionSecretView Permission = "secret_view" + PermissionSecretEdit Permission = "secret_edit" + PermissionSecretDelete Permission = "secret_delete" + PermissionSecretAccess Permission = "secret_access" +) + const ( /* ----- COMMIT CHECK ----- diff --git a/types/enum/scm.go b/types/enum/scm.go new file mode 100644 index 000000000..88e1efdcb --- /dev/null +++ b/types/enum/scm.go @@ -0,0 +1,24 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package enum + +// ScmType defines the different SCM types supported for CI. +type ScmType string + +func (ScmType) Enum() []interface{} { return toInterfaceSlice(scmTypes) } + +var scmTypes = ([]ScmType{ + ScmTypeGitness, + ScmTypeGithub, + ScmTypeGitlab, + ScmTypeUnknown, +}) + +const ( + ScmTypeUnknown ScmType = "UNKNOWN" + ScmTypeGitness ScmType = "GITNESS" + ScmTypeGithub ScmType = "GITHUB" + ScmTypeGitlab ScmType = "GITLAB" +) diff --git a/types/execution.go b/types/execution.go new file mode 100644 index 000000000..a66ff6680 --- /dev/null +++ b/types/execution.go @@ -0,0 +1,46 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package types + +// Execution represents an instance of a pipeline execution. +type Execution struct { + ID int64 `db:"execution_id" json:"id"` + PipelineID int64 `db:"execution_pipeline_id" json:"pipeline_id"` + RepoID int64 `db:"execution_repo_id" json:"repo_id"` + Trigger string `db:"execution_trigger" json:"trigger"` + Number int64 `db:"execution_number" json:"number"` + Parent int64 `db:"execution_parent" json:"parent,omitempty"` + Status string `db:"execution_status" json:"status"` + Error string `db:"execution_error" json:"error,omitempty"` + Event string `db:"execution_event" json:"event"` + Action string `db:"execution_action" json:"action"` + Link string `db:"execution_link" json:"link"` + Timestamp int64 `db:"execution_timestamp" json:"timestamp"` + Title string `db:"execution_title" json:"title,omitempty"` + Message string `db:"execution_message" json:"message"` + Before string `db:"execution_before" json:"before"` + After string `db:"execution_after" json:"after"` + Ref string `db:"execution_ref" json:"ref"` + Fork string `db:"execution_source_repo" json:"source_repo"` + Source string `db:"execution_source" json:"source"` + Target string `db:"execution_target" json:"target"` + Author string `db:"execution_author" json:"author_login"` + AuthorName string `db:"execution_author_name" json:"author_name"` + AuthorEmail string `db:"execution_author_email" json:"author_email"` + AuthorAvatar string `db:"execution_author_avatar" json:"author_avatar"` + Sender string `db:"execution_sender" json:"sender"` + Params string `db:"execution_params" json:"params,omitempty"` + Cron string `db:"execution_cron" json:"cron,omitempty"` + Deploy string `db:"execution_deploy" json:"deploy_to,omitempty"` + DeployID int64 `db:"execution_deploy_id" json:"deploy_id,omitempty"` + Debug bool `db:"execution_debug" json:"debug,omitempty"` + Started int64 `db:"execution_started" json:"started"` + Finished int64 `db:"execution_finished" json:"finished"` + Created int64 `db:"execution_created" json:"created"` + Updated int64 `db:"execution_updated" json:"updated"` + Version int64 `db:"execution_version" json:"version"` + // TODO: (Vistaar) Add stages + // Stages []*Stage `db:"-" json:"stages,omitempty"` +} diff --git a/types/git.go b/types/git.go index c9588858b..3b5fd2bb7 100644 --- a/types/git.go +++ b/types/git.go @@ -74,4 +74,5 @@ type RenameDetails struct { type ListCommitResponse struct { Commits []Commit `json:"commits"` RenameDetails []RenameDetails `json:"rename_details"` + TotalCommits int `json:"total_commits,omitempty"` } diff --git a/types/list_filters.go b/types/list_filters.go new file mode 100644 index 000000000..e7493bbe8 --- /dev/null +++ b/types/list_filters.go @@ -0,0 +1,7 @@ +package types + +// ListQueryFilter has pagination related info and a query param +type ListQueryFilter struct { + Pagination + Query string `json:"query"` +} diff --git a/types/pagination.go b/types/pagination.go new file mode 100644 index 000000000..a00c13333 --- /dev/null +++ b/types/pagination.go @@ -0,0 +1,7 @@ +package types + +// Pagination stores pagination related params +type Pagination struct { + Page int `json:"page"` + Size int `json:"size"` +} diff --git a/types/pipeline.go b/types/pipeline.go new file mode 100644 index 000000000..019bf99a1 --- /dev/null +++ b/types/pipeline.go @@ -0,0 +1,23 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package types + +import "github.com/harness/gitness/types/enum" + +type Pipeline struct { + ID int64 `db:"pipeline_id" json:"id"` + Description string `db:"pipeline_description" json:"description"` + SpaceID int64 `db:"pipeline_space_id" json:"space_id"` + UID string `db:"pipeline_uid" json:"uid"` + Seq int64 `db:"pipeline_seq" json:"seq"` // last execution number for this pipeline + RepoID int64 `db:"pipeline_repo_id" json:"repo_id"` // null if repo_type != gitness + RepoType enum.ScmType `db:"pipeline_repo_type" json:"repo_type"` + RepoName string `db:"pipeline_repo_name" json:"repo_name"` + DefaultBranch string `db:"pipeline_default_branch" json:"default_branch"` + ConfigPath string `db:"pipeline_config_path" json:"config_path"` + Created int64 `db:"pipeline_created" json:"created"` + Updated int64 `db:"pipeline_updated" json:"updated"` + Version int64 `db:"pipeline_version" json:"version"` +} diff --git a/types/repo.go b/types/repo.go index bce22ac2d..c14635992 100644 --- a/types/repo.go +++ b/types/repo.go @@ -57,7 +57,3 @@ type RepositoryGitInfo struct { ID int64 GitUID string } - -func (p *RepositoryGitInfo) Identifier() int64 { - return p.ID -} diff --git a/types/secret.go b/types/secret.go new file mode 100644 index 000000000..547a5fc69 --- /dev/null +++ b/types/secret.go @@ -0,0 +1,29 @@ +// Copyright 2023 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package types + +type Secret struct { + ID int64 `db:"secret_id" json:"id"` + Description string `db:"secret_description" json:"description"` + SpaceID int64 `db:"secret_space_id" json:"space_id"` + UID string `db:"secret_uid" json:"uid"` + Data string `db:"secret_data" json:"-"` + Created int64 `db:"secret_created" json:"created"` + Updated int64 `db:"secret_updated" json:"updated"` + Version int64 `db:"secret_version" json:"version"` +} + +// Copy makes a copy of the secret without the value. +func (s *Secret) CopyWithoutData() *Secret { + return &Secret{ + ID: s.ID, + Description: s.Description, + UID: s.UID, + SpaceID: s.SpaceID, + Created: s.Created, + Updated: s.Updated, + Version: s.Version, + } +} diff --git a/web/.env b/web/.env new file mode 100644 index 000000000..ceb9351b5 --- /dev/null +++ b/web/.env @@ -0,0 +1,3 @@ +STANDALONE=true +PORT=3020 +API_URL=http://localhost:3000 diff --git a/web/.gitignore b/web/.gitignore deleted file mode 100644 index 5d4a959bc..000000000 --- a/web/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -dist -coverage -.env -yarn-error* -.DS_Store \ No newline at end of file diff --git a/web/dist.go b/web/dist.go index 58adb79c5..7ff754f9f 100644 --- a/web/dist.go +++ b/web/dist.go @@ -11,6 +11,7 @@ import ( "io/fs" "net/http" "os" + "path" "path/filepath" "time" @@ -36,6 +37,7 @@ func Handler() http.HandlerFunc { // http.FileServer to always load the index.html // file if a directory path is being requested. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // because this is a single page application, // we need to always load the index.html file // in the root of the project, unless the path @@ -44,6 +46,9 @@ func Handler() http.HandlerFunc { // HACK: alter the path to point to the // root of the project. r.URL.Path = "/" + } else { + // All static assets are served from the root path + r.URL.Path = "/" + path.Base(r.URL.Path) } // Disable caching and sniffing via HTTP headers for UI main entry resources diff --git a/web/package.json b/web/package.json index daf7a3b8f..9a5301e0e 100644 --- a/web/package.json +++ b/web/package.json @@ -42,7 +42,7 @@ "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.9.6", "@harness/design-system": "1.4.0", - "@harness/icons": "1.149.0", + "@harness/icons": "1.174.0", "@harness/uicore": "3.131.1", "@harness/use-modal": "1.1.0", "@popperjs/core": "^2.4.2", @@ -60,6 +60,7 @@ "github-markdown-css": "^5.1.0", "highcharts": "9.1.0", "highcharts-react-official": "3.0.0", + "iconoir-react": "^6.11.0", "immer": "^9.0.6", "lang-map": "^0.4.0", "lodash-es": "^4.17.15", diff --git a/web/src/App.module.scss b/web/src/App.module.scss index 1e9464c87..0cedaa961 100644 --- a/web/src/App.module.scss +++ b/web/src/App.module.scss @@ -1,66 +1,19 @@ @import 'src/utils/vars'; -/* - * NOTE: Styles in this file are loaded in both standalone and embedded - * versions. Be careful! Don't introduce global states that could be conflict - * with Harness Platform and other modules. - */ - .main { @include vars; &.fullPage { - height: 100%; + height: var(--page-height); } :global { - .Resizer { - background-color: var(--grey-300); - opacity: 0.2; - z-index: 1; - box-sizing: border-box; - background-clip: padding-box; - } - - .Resizer:hover { - transition: all 2s ease; - } - - .Resizer.horizontal { - margin: -5px 0; - border-top: 5px solid rgba(255, 255, 255, 0); - border-bottom: 5px solid rgba(255, 255, 255, 0); - cursor: row-resize; - } - - .Resizer.horizontal:hover { - border-top: 5px solid rgba(0, 0, 0, 0.5); - border-bottom: 5px solid rgba(0, 0, 0, 0.5); - } - - .Resizer.vertical { - width: 11px; - margin: 0 -5px; - border-left: 5px solid rgba(255, 255, 255, 0); - border-right: 5px solid rgba(255, 255, 255, 0); - cursor: col-resize; - } - - .Resizer.vertical:hover { - border-left: 5px solid rgba(0, 0, 0, 0.5); - border-right: 5px solid rgba(0, 0, 0, 0.5); - } - - .Resizer.disabled { - cursor: not-allowed; - } - - .Resizer.disabled:hover { - border-color: transparent; - } - div[data-testid='page-body'] > div[data-testid='page-error'] { - height: 80vh !important; + height: 70vh !important; + } + + .PageBody--pageBody { + min-height: calc(var(--page-height) - var(--page-header-height, 64px)); } } } diff --git a/web/src/App.tsx b/web/src/App.tsx index 18f99cd99..709f6a6b3 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState, useCallback, useMemo } from 'react' import { RestfulProvider } from 'restful-react' +import { IconoirProvider } from 'iconoir-react' import cx from 'classnames' import { Container } from '@harness/uicore' import { ModalProvider } from '@harness/use-modal' @@ -72,7 +73,14 @@ const App: React.FC = React.memo(function App({ currentUser: defaultCurrentUser, currentUserProfileURL }}> - {props.children ? props.children : } + + {props.children ? props.children : } + diff --git a/web/src/RouteDefinitions.ts b/web/src/RouteDefinitions.ts index aa34eb9a6..f1570a9ec 100644 --- a/web/src/RouteDefinitions.ts +++ b/web/src/RouteDefinitions.ts @@ -13,6 +13,8 @@ export interface CODEProps { webhookId?: string pipeline?: string execution?: string + commitSHA?: string + secret?: string } export interface CODEQueryProps { @@ -27,10 +29,12 @@ export const pathProps: Readonly, 'repoPath' | 'branch' commitRef: ':commitRef*', diffRefs: ':diffRefs*', pullRequestId: ':pullRequestId', - pullRequestSection: ':pullRequestSection*', + pullRequestSection: ':pullRequestSection', webhookId: ':webhookId', pipeline: ':pipeline', - execution: ':execution' + execution: ':execution', + commitSHA: ':commitSHA', + secret: ':secret' } export interface CODERoutes { @@ -57,7 +61,7 @@ export interface CODERoutes { toCODEPullRequests: (args: Required>) => string toCODEPullRequest: ( args: RequiredField< - Pick, + Pick, 'repoPath' | 'pullRequestId' > ) => string @@ -71,8 +75,18 @@ export interface CODERoutes { toCODEExecutions: (args: Required>) => string toCODEExecution: (args: Required>) => string + toCODESecret: (args: Required>) => string } +/** + * NOTE: NEVER IMPORT AND USE THIS ROUTES EXPORT DIRECTLY IN CODE. + * + * routes is used to created URLs in standalone version. Instead, use + * the `routes` from AppContext which is mapped to this export in standalone + * version or Harness Platform routes which is passed from Harness Platform UI. + * + * Correct usage: const { routes } = useAppContext() + */ export const routes: CODERoutes = { toSignIn: (): string => '/signin', toRegister: (): string => '/register', @@ -102,8 +116,10 @@ export const routes: CODERoutes = { toCODECommits: ({ repoPath, commitRef }) => `/${repoPath}/commits/${commitRef}`, toCODECommit: ({ repoPath, commitRef }) => `/${repoPath}/commit/${commitRef}`, toCODEPullRequests: ({ repoPath }) => `/${repoPath}/pulls`, - toCODEPullRequest: ({ repoPath, pullRequestId, pullRequestSection }) => - `/${repoPath}/pulls/${pullRequestId}${pullRequestSection ? '/' + pullRequestSection : ''}`, + toCODEPullRequest: ({ repoPath, pullRequestId, pullRequestSection, commitSHA }) => + `/${repoPath}/pulls/${pullRequestId}${pullRequestSection ? '/' + pullRequestSection : ''}${ + commitSHA ? '/' + commitSHA : '' + }`, toCODECompare: ({ repoPath, diffRefs }) => `/${repoPath}/pulls/compare/${diffRefs}`, toCODEBranches: ({ repoPath }) => `/${repoPath}/branches`, toCODETags: ({ repoPath }) => `/${repoPath}/tags`, @@ -113,5 +129,7 @@ export const routes: CODERoutes = { toCODEWebhookDetails: ({ repoPath, webhookId }) => `/${repoPath}/webhook/${webhookId}`, toCODEExecutions: ({ space, pipeline }) => `/pipelines/${space}/pipeline/${pipeline}`, - toCODEExecution: ({ space, pipeline, execution }) => `/pipelines/${space}/pipeline/${pipeline}/execution/${execution}` + toCODEExecution: ({ space, pipeline, execution }) => + `/pipelines/${space}/pipeline/${pipeline}/execution/${execution}`, + toCODESecret: ({ space, secret }) => `/secrets/${space}/secret/${secret}` } diff --git a/web/src/RouteDestinations.tsx b/web/src/RouteDestinations.tsx index 6b8423925..76fb7aaaa 100644 --- a/web/src/RouteDestinations.tsx +++ b/web/src/RouteDestinations.tsx @@ -30,6 +30,7 @@ import { useStrings } from 'framework/strings' import { useFeatureFlag } from 'hooks/useFeatureFlag' import ExecutionList from 'pages/ExecutionList/ExecutionList' import Execution from 'pages/Execution/Execution' +import Secret from 'pages/Secret/Secret' export const RouteDestinations: React.FC = React.memo(function RouteDestinations() { const { getString } = useStrings() @@ -100,6 +101,12 @@ export const RouteDestinations: React.FC = React.memo(function RouteDestinations )} + {OPEN_SOURCE_SECRETS && ( + + + + + + )} + {OPEN_SOURCE_SECRETS && ( diff --git a/web/src/components/BranchTagSelect/BranchTagSelect.module.scss b/web/src/components/BranchTagSelect/BranchTagSelect.module.scss index 4ebf838fa..c73704305 100644 --- a/web/src/components/BranchTagSelect/BranchTagSelect.module.scss +++ b/web/src/components/BranchTagSelect/BranchTagSelect.module.scss @@ -8,6 +8,7 @@ html[class=''] { --border: 1px solid var(--grey-200) !important; --background-color-active: var(--white) !important; --box-shadow: none !important; + white-space: nowrap !important; &:active, &:hover, @@ -60,8 +61,15 @@ html[class=''] { .listContainer { min-height: 50px; - max-height: 300px; + max-height: 200px; overflow-y: auto; + + :global { + a.bp3-menu-item:hover { + background: var(--primary-1) !important; + color: var(--grey-1000) !important; + } + } } .newBtnText { diff --git a/web/src/components/BranchTagSelect/BranchTagSelect.tsx b/web/src/components/BranchTagSelect/BranchTagSelect.tsx index 0894d8a3c..77811949a 100644 --- a/web/src/components/BranchTagSelect/BranchTagSelect.tsx +++ b/web/src/components/BranchTagSelect/BranchTagSelect.tsx @@ -30,6 +30,7 @@ export interface BranchTagSelectProps extends Omit, Pic forBranchesOnly?: boolean labelPrefix?: string placeHolder?: string + popoverClassname?: string } export const BranchTagSelect: React.FC = ({ @@ -42,6 +43,8 @@ export const BranchTagSelect: React.FC = ({ forBranchesOnly, labelPrefix, placeHolder, + className, + popoverClassname, ...props }) => { const [query, onQuery] = useState('') @@ -50,7 +53,7 @@ export const BranchTagSelect: React.FC = ({ return ( - )} + diff --git a/web/src/components/CloneCredentialDialog/CloneCredentialDialog.tsx b/web/src/components/CloneCredentialDialog/CloneCredentialDialog.tsx index 7e5105c33..583ff564c 100644 --- a/web/src/components/CloneCredentialDialog/CloneCredentialDialog.tsx +++ b/web/src/components/CloneCredentialDialog/CloneCredentialDialog.tsx @@ -10,6 +10,7 @@ import { Text, useToaster } from '@harness/uicore' +import { useMutate } from 'restful-react' import { useHistory } from 'react-router-dom' import { useStrings } from 'framework/strings' import { CopyButton } from 'components/CopyButton/CopyButton' @@ -27,20 +28,31 @@ const CloneCredentialDialog = (props: CloneCredentialDialogProps) => { const { setFlag, flag } = props const history = useHistory() const { getString } = useStrings() - const { hooks, currentUser, currentUserProfileURL } = useAppContext() + const { hooks, currentUser, currentUserProfileURL, standalone, routes } = useAppContext() const [token, setToken] = useState('') const { showError } = useToaster() const hash = generateAlphaNumericHash(6) - - const tokenData = hooks?.useGenerateToken?.(hash, currentUser.uid, flag) + const { mutate } = useMutate({ path: '/api/v1/user/tokens', verb: 'POST' }) + const genToken = async (props: { uid: string }) => { + const res = await mutate({ uid: props.uid }) + try { + setToken(res?.access_token) + } catch { + showError(res?.data?.message || res?.message) + } + return res + } + const tokenData = standalone ? false : hooks?.useGenerateToken?.(hash, currentUser.uid, flag) useEffect(() => { if (tokenData) { if (tokenData && tokenData?.status !== 400) { setToken(tokenData?.data) } else if (tokenData?.status === 400 && flag) { - setToken('N/A') showError(tokenData?.data?.message || tokenData?.message) } + } else if (!tokenData && standalone && flag) { + let payload = { uid: `code_token_${hash}` } + genToken(payload) } }, [flag, tokenData, showError]) return ( @@ -88,7 +100,7 @@ const CloneCredentialDialog = (props: CloneCredentialDialogProps) => { ) - const columns: Column[] = useMemo( + const columns: Column[] = useMemo( () => [ { Header: getString('repos.name'), width: 'calc(100% - 180px)', - Cell: ({ row }: CellProps) => { + Cell: ({ row }: CellProps) => { const record = row.original return ( - - {record.uid} - - {record.description && {record.description}} + {record.number} + {record.status && {record.status}} @@ -108,63 +80,64 @@ const ExecutionList = () => { { Header: getString('repos.updated'), width: '180px', - Cell: ({ row }: CellProps) => { + Cell: ({ row }: CellProps) => { return ( {formatDate(row.original.updated as number)} - {row.original.isPublic === false ? : undefined} ) }, disableSortBy: true } ], - [getString, searchTerm] + [getString] ) return ( executions.length === 0, + when: () => executions?.length === 0, image: noExecutionImage, message: getString('executions.noData'), button: NewExecutionButton }}> - + {NewExecutionButton} - {!!executions?.length && ( - + className={css.table} columns={columns} data={executions || []} onRowClick={executionInfo => history.push( routes.toCODEExecution({ - space: executionInfo.spaceUid, - pipeline: executionInfo.pipelineUid, - execution: executionInfo.uid + space, + pipeline: pipeline as string, + execution: String(executionInfo.id) }) ) } - getRowClassName={row => cx(css.row, !row.original.description && css.noDesc)} + getRowClassName={row => cx(css.row, !row.original.number && css.noDesc)} /> )} - !!executions.length && !!searchTerm?.length} forSearch={true} /> + !!executions && executions.length === 0} forSearch={true} /> - {/* */} + diff --git a/web/src/pages/Home/Home.module.scss b/web/src/pages/Home/Home.module.scss index 04468c9cf..4594e5b56 100644 --- a/web/src/pages/Home/Home.module.scss +++ b/web/src/pages/Home/Home.module.scss @@ -1,5 +1,5 @@ .main { - min-height: var(--page-min-height, 100%); + min-height: var(--page-height); background-color: var(--primary-bg) !important; } .container { diff --git a/web/src/pages/PipelineList/PipelineList.module.scss b/web/src/pages/PipelineList/PipelineList.module.scss index 328cc4ede..dfdc0aec4 100644 --- a/web/src/pages/PipelineList/PipelineList.module.scss +++ b/web/src/pages/PipelineList/PipelineList.module.scss @@ -1,5 +1,5 @@ .main { - min-height: var(--page-min-height, 100%); + min-height: var(--page-height); background-color: var(--primary-bg) !important; .layout { @@ -7,6 +7,10 @@ } } +.withError { + display: grid; +} + .table { [class*='TableV2--header'] [class*='variation-table-headers'] { text-transform: none; diff --git a/web/src/pages/PipelineList/PipelineList.module.scss.d.ts b/web/src/pages/PipelineList/PipelineList.module.scss.d.ts index bcb8f1c05..c83440288 100644 --- a/web/src/pages/PipelineList/PipelineList.module.scss.d.ts +++ b/web/src/pages/PipelineList/PipelineList.module.scss.d.ts @@ -3,6 +3,7 @@ declare const styles: { readonly main: string readonly layout: string + readonly withError: string readonly table: string readonly row: string readonly noDesc: string diff --git a/web/src/pages/PipelineList/PipelineList.tsx b/web/src/pages/PipelineList/PipelineList.tsx index 9ba37bb3c..07173b538 100644 --- a/web/src/pages/PipelineList/PipelineList.tsx +++ b/web/src/pages/PipelineList/PipelineList.tsx @@ -5,7 +5,6 @@ import { Color, Container, FlexExpander, - Icon, Layout, PageBody, PageHeader, @@ -16,61 +15,41 @@ import cx from 'classnames' import type { CellProps, Column } from 'react-table' import Keywords from 'react-keywords' import { useHistory } from 'react-router-dom' +import { useGet } from 'restful-react' import { useStrings } from 'framework/strings' import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner' import { NoResultCard } from 'components/NoResultCard/NoResultCard' -import { formatDate } from 'utils/Utils' -import { routes } from 'RouteDefinitions' +import { LIST_FETCHING_LIMIT, PageBrowserProps, formatDate, getErrorMessage, voidFn } from 'utils/Utils' +import { useGetSpaceParam } from 'hooks/useGetSpaceParam' +import type { TypesPipeline } from 'services/code' +import { useQueryParams } from 'hooks/useQueryParams' +import { usePageIndex } from 'hooks/usePageIndex' +import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination' +import { useAppContext } from 'AppContext' import noPipelineImage from '../RepositoriesListing/no-repo.svg' import css from './PipelineList.module.scss' -interface Pipeline { - id: number - uid: string - name: string - updated: number - description?: string - isPublic?: boolean - spaceUid: string -} - -const pipelines: Pipeline[] = [ - { - id: 1, - uid: 'pipeline-1', - name: 'Pipeline 1', - updated: 1687234800, - description: 'This is a description', - isPublic: true, - spaceUid: 'root' - }, - { - id: 2, - uid: 'pipeline-2', - name: 'Pipeline 2', - updated: 1730275200, - description: 'This is a description', - isPublic: true, - spaceUid: 'root' - }, - { - id: 3, - uid: 'pipeline-3', - name: 'Pipeline 3', - updated: 1773315600, - description: 'This is a description', - isPublic: false, - spaceUid: 'root' - } -] - -const loading = false - const PipelineList = () => { + const { routes } = useAppContext() + const space = useGetSpaceParam() const history = useHistory() const { getString } = useStrings() const [searchTerm, setSearchTerm] = useState() + const pageBrowser = useQueryParams() + const pageInit = pageBrowser.page ? parseInt(pageBrowser.page) : 1 + const [page, setPage] = usePageIndex(pageInit) + + const { + data: pipelines, + error, + loading, + refetch, + response + } = useGet({ + path: `/api/v1/spaces/${space}/pipelines`, + queryParams: { page, limit: LIST_FETCHING_LIMIT, query: searchTerm } + }) const NewPipelineButton = ( ) - const columns: Column[] = useMemo( + const columns: Column[] = useMemo( () => [ { Header: getString('pipelines.name'), width: 'calc(100% - 180px)', - Cell: ({ row }: CellProps) => { + Cell: ({ row }: CellProps) => { const record = row.original return ( @@ -104,13 +83,12 @@ const PipelineList = () => { { Header: getString('repos.updated'), width: '180px', - Cell: ({ row }: CellProps) => { + Cell: ({ row }: CellProps) => { return ( {formatDate(row.original.updated as number)} - {row.original.isPublic === false ? : undefined} ) }, @@ -124,8 +102,11 @@ const PipelineList = () => { pipelines.length === 0, + when: () => pipelines?.length === 0 && searchTerm === undefined, image: noPipelineImage, message: getString('pipelines.noData'), button: NewPipelineButton @@ -141,20 +122,23 @@ const PipelineList = () => { {!!pipelines?.length && ( - + className={css.table} columns={columns} data={pipelines || []} onRowClick={pipelineInfo => - history.push(routes.toCODEExecutions({ space: pipelineInfo.spaceUid, pipeline: pipelineInfo.uid })) + history.push(routes.toCODEExecutions({ space, pipeline: pipelineInfo.uid as string })) } getRowClassName={row => cx(css.row, !row.original.description && css.noDesc)} /> )} - !!pipelines.length && !!searchTerm?.length} forSearch={true} /> + !!pipelines && pipelines?.length === 0 && !!searchTerm?.length} + forSearch={true} + /> - {/* */} + diff --git a/web/src/pages/PullRequest/Checks/Checks.module.scss b/web/src/pages/PullRequest/Checks/Checks.module.scss index e9290a16c..fc977ee48 100644 --- a/web/src/pages/PullRequest/Checks/Checks.module.scss +++ b/web/src/pages/PullRequest/Checks/Checks.module.scss @@ -1,71 +1,59 @@ .main { - --stage-title-height: 46px; - --stage-detail-section-height: 40px; + --stage-title-height: 54px; + --stage-detail-section-height: 48px; + --log-content-header-height: 64px; background-color: var(--white) !important; - min-height: calc(var(--page-min-height) - 144px); - height: calc(100vh - 144px); + min-height: calc(var(--page-min-height) - 156px); + height: calc(var(--page-height) - 156px); overflow: hidden; > div { position: relative !important; - .stagesContainer, - .terminalContainer { + .menu, + .content { width: 100%; height: 100%; } - .stagesContainer { - .stagesHeader { - height: var(--stage-title-height); - align-items: center; - padding: 0 var(--spacing-medium) 0 var(--spacing-xlarge); - border-bottom: 1px solid var(--grey-200); + .menu { + .menuItem { + border-bottom: 1px solid var(--grey-100); - span[data-stage-state] { - width: 18px; - height: 18px; - display: inline-block; - background-color: var(--grey-200); - - &[data-stage-state='success'] { - background-color: var(--green-500); - } - - &[data-stage-state='failed'] { - background-color: var(--red-500); - } - - &[data-stage-state='running'] { - background-color: var(--yellow-500); - } - } - } - - .stageSection { - border-bottom: 1px solid var(--grey-200); - padding: 0 0 0 var(--spacing-xlarge); - cursor: pointer; - - &.expanded { - .chevron { - transform: rotate(90deg); - } - } - - .chevron { - transition: transform 0.2s ease; - } - - .sectionName { + .layout { display: flex; align-items: center; min-height: var(--stage-title-height); - padding-right: var(--spacing-medium); + padding: 0 var(--spacing-medium) 0 var(--spacing-xlarge); + + &.expanded { + .chevron { + transform: rotate(90deg); + } + } + + .chevron { + transition: transform 0.2s ease; + } + + &:hover, + &.selected { + background-color: var(--primary-1); + } + + &.selected .uid { + color: var(--primary-7) !important; + } + + .uid { + color: var(--grey-700) !important; + font-weight: 600 !important; + font-size: 13px !important; + } } - .stageSectionDetails { + .subMenu { cursor: pointer; height: var(--stage-detail-section-height); align-items: center; @@ -75,7 +63,10 @@ border-top-left-radius: 6px; border-bottom-left-radius: 6px; - &:hover { + &:hover, + &.selected { + background-color: var(--grey-100); + .text { color: var(--primary-7); } @@ -97,24 +88,114 @@ } } - .terminalContainer { - overflow: hidden; + .content { background-color: var(--black); - :global { - .terminal.xterm { - padding: var(--spacing-medium); + &.markdown { + :global { + .wmde-markdown { + background-color: transparent !important; + } } - // .terminal-container { - // overflow: hidden; - // } + padding: 0 var(--spacing-large) var(--spacing-medium); + overflow: auto; + } - // .xterm .xterm-viewport { - // /* see : https://github.com/xtermjs/xterm.js/issues/3564#issuecomment-1004417440 */ - // width: initial !important; - // } + &.terminal { + overflow: hidden; + + .header { + padding: var(--spacing-medium) var(--spacing-large) 0; + } + + :global { + .terminal.xterm { + padding: var(--spacing-small) var(--spacing-large) 0; + } + } + } + + .header { + padding-top: var(--spacing-medium); + position: sticky; + top: 0; + background-color: var(--black); + height: var(--log-content-header-height); + + .headerLayout { + border-bottom: 1px solid var(--grey-800); + padding-bottom: var(--spacing-medium); + align-items: center; + } + } + + .markdownContainer { + padding-top: var(--spacing-medium); + } + + .terminalContainer { + height: calc(100% - var(--log-content-header-height)); } } } + + :global { + .Resizer { + background-color: var(--grey-300); + opacity: 0.2; + z-index: 1; + box-sizing: border-box; + background-clip: padding-box; + } + + .Resizer:hover { + transition: all 2s ease; + } + + .Resizer.horizontal { + margin: -5px 0; + border-top: 5px solid rgba(255, 255, 255, 0); + border-bottom: 5px solid rgba(255, 255, 255, 0); + cursor: row-resize; + } + + .Resizer.horizontal:hover { + border-top: 5px solid rgba(0, 0, 0, 0.5); + border-bottom: 5px solid rgba(0, 0, 0, 0.5); + } + + .Resizer.vertical { + width: 11px; + margin: 0 -5px; + border-left: 5px solid rgba(255, 255, 255, 0); + border-right: 5px solid rgba(255, 255, 255, 0); + cursor: col-resize; + } + + .Resizer.vertical:hover { + border-left: 5px solid rgba(0, 0, 0, 0.5); + border-right: 5px solid rgba(0, 0, 0, 0.5); + } + + .Resizer.disabled { + cursor: not-allowed; + } + + .Resizer.disabled:hover { + border-color: transparent; + } + } +} + +.status { + align-self: center; + + &.invert { + filter: invert(100%); + } +} + +.noShrink { + flex-shrink: inherit; } diff --git a/web/src/pages/PullRequest/Checks/Checks.module.scss.d.ts b/web/src/pages/PullRequest/Checks/Checks.module.scss.d.ts index db8a612f9..7473ee894 100644 --- a/web/src/pages/PullRequest/Checks/Checks.module.scss.d.ts +++ b/web/src/pages/PullRequest/Checks/Checks.module.scss.d.ts @@ -2,15 +2,25 @@ // this is an auto-generated file declare const styles: { readonly main: string - readonly stagesContainer: string - readonly terminalContainer: string - readonly stagesHeader: string - readonly stageSection: string + readonly menu: string + readonly content: string + readonly menuItem: string + readonly layout: string readonly expanded: string readonly chevron: string - readonly sectionName: string - readonly stageSectionDetails: string + readonly selected: string + readonly uid: string + readonly subMenu: string readonly text: string readonly active: string + readonly markdown: string + readonly terminal: string + readonly header: string + readonly headerLayout: string + readonly markdownContainer: string + readonly terminalContainer: string + readonly status: string + readonly invert: string + readonly noShrink: string } export default styles diff --git a/web/src/pages/PullRequest/Checks/Checks.tsx b/web/src/pages/PullRequest/Checks/Checks.tsx index 87e0eaafe..9b04313a1 100644 --- a/web/src/pages/PullRequest/Checks/Checks.tsx +++ b/web/src/pages/PullRequest/Checks/Checks.tsx @@ -1,93 +1,282 @@ -import React, { useCallback, useRef } from 'react' -import { Render } from 'react-jsx-match' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Falsy, Match, Render, Truthy } from 'react-jsx-match' +import { CheckCircle, NavArrowRight } from 'iconoir-react' import SplitPane from 'react-split-pane' +import { get } from 'lodash-es' import cx from 'classnames' -import { Container, Layout, Text, Color, FlexExpander, Icon, useToggle } from '@harness/uicore' +import { useHistory } from 'react-router-dom' +import { + Container, + Layout, + Text, + Color, + FlexExpander, + Icon, + useToggle, + FontVariation, + Utils, + Button, + ButtonVariation, + ButtonSize +} from '@harness/uicore' import { LogViewer, TermRefs } from 'components/LogViewer/LogViewer' -import { ButtonRoleProps } from 'utils/Utils' +import { ButtonRoleProps, PullRequestCheckType, PullRequestSection, timeDistance } from 'utils/Utils' +import type { GitInfoProps } from 'utils/GitUtils' +import { useAppContext } from 'AppContext' +import { useQueryParams } from 'hooks/useQueryParams' +import { useStrings } from 'framework/strings' +import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer' +import type { PRChecksDecisionResult } from 'hooks/usePRChecksDecision' +import type { TypesCheck } from 'services/code' +import { PRCheckExecutionState, PRCheckExecutionStatus } from 'components/PRCheckExecutionStatus/PRCheckExecutionStatus' import css from './Checks.module.scss' -// interface ChecksProps {} +interface ChecksProps extends Pick { + prChecksDecisionResult?: PRChecksDecisionResult +} -export function Checks() { +export const Checks: React.FC = props => { + const { getString } = useStrings() const termRefs = useRef() const onSplitPaneResized = useCallback(() => termRefs.current?.fitAddon?.fit(), []) + const [selectedItemData, setSelectedItemData] = useState() + const isCheckDataMarkdown = useMemo( + () => selectedItemData?.payload?.kind === PullRequestCheckType.MARKDOWN, + [selectedItemData?.payload?.kind] + ) + const logContent = useMemo( + () => get(selectedItemData, 'payload.data.details', selectedItemData?.summary || ''), + [selectedItemData] + ) + + if (!props.prChecksDecisionResult) { + return null + } return ( - - - - - - - - ) -} - -const StagesContainer: React.FC = () => { - return ( - - - - - - - - - - - + + + + { + setTimeout(() => setSelectedItemData(data), 0) + }} + /> + + + + + + + {selectedItemData?.uid} + + + + - )} + + > = URL: ( { - history.push(currentUserProfileURL) + history.push(standalone ? routes.toCODEUserProfile() : currentUserProfileURL) }}> here @@ -142,7 +141,7 @@ export const EmptyRepositoryInfo: React.FC> = source={getString('repoEmptyMarkdownExisting') .replace(/REPO_URL/g, repoMetadata.git_url || '') .replace(/REPO_NAME/g, repoMetadata.uid || '') - .replace(/CREATE_API_TOKEN_URL/g, currentUserProfileURL || '') + .replace(/CREATE_API_TOKEN_URL/g, standalone ? routes.toCODEUserProfile() : currentUserProfileURL || '') .replace(/DEFAULT_BRANCH/g, repoMetadata.default_branch || '')} /> diff --git a/web/src/pages/Repository/Repository.module.scss b/web/src/pages/Repository/Repository.module.scss index fa57f47e7..a955be2ab 100644 --- a/web/src/pages/Repository/Repository.module.scss +++ b/web/src/pages/Repository/Repository.module.scss @@ -1,11 +1,11 @@ .main { - min-height: var(--page-min-height, 100%); + min-height: var(--page-height); background-color: var(--white) !important; &.withFileViewer { &, > div:first-of-type { - min-height: var(--page-min-height, 100vh) !important; + min-height: var(--page-height) !important; } > div:first-of-type { diff --git a/web/src/pages/Repository/Repository.tsx b/web/src/pages/Repository/Repository.tsx index 5e7c178a8..822bf98f1 100644 --- a/web/src/pages/Repository/Repository.tsx +++ b/web/src/pages/Repository/Repository.tsx @@ -26,13 +26,7 @@ export default function Repository() { const [fileNotExist, setFileNotExist] = useState(false) const { getString } = useStrings() - useEffect(() => { - if (resourceError?.status === 404) { - setFileNotExist(true) - } else { - setFileNotExist(false) - } - }, [resourceError]) + useEffect(() => setFileNotExist(resourceError?.status === 404), [resourceError]) return ( diff --git a/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.module.scss b/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.module.scss index 4997302a0..b5fb7d766 100644 --- a/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.module.scss +++ b/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.module.scss @@ -45,4 +45,36 @@ padding: var(--spacing-xxlarge) !important; } } + + .commitMsgLayout { + align-items: center; + padding-right: var(--spacing-large); + } + + .linkContainer { + display: inline-block; + + &.noShrink { + flex-shrink: inherit; + } + + .link { + color: var(--primary-7) !important; + + &, + .text { + font-size: 13px; + font-weight: 400; + cursor: pointer !important; + } + + .text:hover { + color: var(--primary-7) !important; + } + + .text.hightlight { + color: var(--primary-7) !important; + } + } + } } diff --git a/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.module.scss.d.ts b/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.module.scss.d.ts index f320298fe..123aa6133 100644 --- a/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.module.scss.d.ts +++ b/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.module.scss.d.ts @@ -8,5 +8,11 @@ declare const styles: { readonly readmeContainer: string readonly heading: string readonly readmeContent: string + readonly commitMsgLayout: string + readonly linkContainer: string + readonly noShrink: string + readonly link: string + readonly text: string + readonly hightlight: string } export default styles diff --git a/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.tsx b/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.tsx index 6d6d62966..283098f03 100644 --- a/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.tsx +++ b/web/src/pages/Repository/RepositoryContent/FolderContent/FolderContent.tsx @@ -1,68 +1,66 @@ -import React, { useMemo } from 'react' -import { Container, Color, TableV2 as Table, Text, Utils } from '@harness/uicore' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { + Container, + Color, + TableV2 as Table, + Text, + Utils, + StringSubstitute, + Layout, + TextProps, + Icon, + useIsMounted +} from '@harness/uicore' +import cx from 'classnames' import type { CellProps, Column } from 'react-table' import { Render } from 'react-jsx-match' -import { sortBy } from 'lodash-es' -import { useHistory } from 'react-router-dom' +import { chunk, sortBy, throttle } from 'lodash-es' +import { useMutate } from 'restful-react' +import { Link, useHistory } from 'react-router-dom' import { useAppContext } from 'AppContext' -import type { OpenapiContentInfo, OpenapiDirContent } from 'services/code' -import { formatDate } from 'utils/Utils' +import type { OpenapiContentInfo, OpenapiDirContent, TypesCommit } from 'services/code' +import { formatDate, isInViewport, LIST_FETCHING_LIMIT } from 'utils/Utils' import { findReadmeInfo, CodeIcon, GitInfoProps, isFile } from 'utils/GitUtils' import { LatestCommitForFolder } from 'components/LatestCommit/LatestCommit' +import { useEventListener } from 'hooks/useEventListener' import { Readme } from './Readme' +import repositoryCSS from '../../Repository.module.scss' import css from './FolderContent.module.scss' -export function FolderContent({ - repoMetadata, - resourceContent, - gitRef -}: Pick) { +type FolderContentProps = Pick + +export function FolderContent({ repoMetadata, resourceContent, gitRef }: FolderContentProps) { const history = useHistory() - const { routes } = useAppContext() + const { routes, standalone } = useAppContext() const columns: Column[] = useMemo( () => [ { id: 'name', - width: '40%', - Cell: ({ row }: CellProps) => { - return ( - - {row.original.name} - - ) - } + width: '30%', + Cell: ({ row }: CellProps) => ( + + + + + + + ) }, { id: 'message', - width: 'calc(60% - 100px)', - Cell: ({ row }: CellProps) => { - return ( - - { - history.push( - routes.toCODECommit({ - repoPath: repoMetadata.path as string, - commitRef: row.original.latest_commit?.sha as string - }) - ) - }}> - {row.original.latest_commit?.title} - - - ) - } + width: 'calc(70% - 100px)', + Cell: ({ row }: CellProps) => ( + + ) }, { id: 'when', @@ -79,6 +77,106 @@ export function FolderContent({ [] // eslint-disable-line react-hooks/exhaustive-deps ) const readmeInfo = useMemo(() => findReadmeInfo(resourceContent), [resourceContent]) + const scrollDOMElement = useMemo( + () => (standalone ? document.querySelector(`.${repositoryCSS.main}`)?.parentElement : window) as HTMLElement, + [standalone] + ) + const resourceEntries = useMemo( + () => sortBy((resourceContent.content as OpenapiDirContent)?.entries || [], ['type', 'name']), + [resourceContent.content] + ) + const [pathsChunks, setPathsChunks] = useState([]) + const { mutate: fetchLastCommitsForPaths } = useMutate({ + verb: 'POST', + path: `/api/v1/repos/${encodeURIComponent(repoMetadata.path as string)}/path-details`, + queryParams: { + git_ref: gitRef + } + }) + const lastCommitMapping = useRef>({}) + const mergedContentEntries = useMemo( + () => + resourceEntries.map(entry => ({ + ...entry, + latest_commit: lastCommitMapping.current[entry.path as string] || entry.latest_commit + })), + [resourceEntries, pathsChunks] // eslint-disable-line react-hooks/exhaustive-deps + ) + const isMounted = useIsMounted() + + // The idea is to fetch last commit details for chunks that has atleast one path which is + // rendered in the viewport + // eslint-disable-next-line react-hooks/exhaustive-deps + const scrollCallback = useCallback( + throttle(() => { + if (isMounted.current) { + for (const pathsChunk of pathsChunks) { + const { paths, loaded, loading, failed } = pathsChunk + + if (!loaded && !loading && !failed) { + for (let i = 0; i < paths.length; i++) { + const element = document.querySelector(`[data-resource-path="${paths[i]}"]`) + + if (element && isInViewport(element)) { + pathsChunk.loading = true + + if (isMounted.current) { + setPathsChunks(pathsChunks.map(_chunk => (pathsChunk === _chunk ? pathsChunk : _chunk))) + + fetchLastCommitsForPaths({ paths }) + .then(response => { + pathsChunk.loaded = true + + if (isMounted.current) { + response?.details?.forEach(({ path, last_commit }) => { + lastCommitMapping.current[path] = last_commit + }) + + setPathsChunks(pathsChunks.map(_chunk => (pathsChunk === _chunk ? pathsChunk : _chunk))) + } + }) + .catch(error => { + pathsChunk.loaded = false + pathsChunk.loading = false + pathsChunk.failed = true + + if (isMounted.current) { + setPathsChunks(pathsChunks.map(_chunk => (pathsChunk === _chunk ? pathsChunk : _chunk))) + } + + console.log('Failed to fetch path commit details', error) // eslint-disable-line no-console + }) + } + break + } + } + } + } + } + }, 50), + [pathsChunks, setPathsChunks] + ) + + // Group all resourceEntries paths into chunks, each has LIST_FETCHING_LIMIT paths + useEffect(() => { + setPathsChunks( + chunk(resourceEntries.map(entry => entry.path as string) || [], LIST_FETCHING_LIMIT).map(paths => ({ + paths, + loaded: false, + loading: false, + failed: false + })) + ) + lastCommitMapping.current = {} + }, [resourceEntries]) + + useEventListener('scroll', scrollCallback, scrollDOMElement) + + // Trigger scroll event callback on mount and cancel it on unmount + useEffect(() => { + scrollCallback() + return () => scrollCallback.cancel() + }, [scrollCallback]) return ( @@ -88,13 +186,13 @@ export function FolderContent({ className={css.table} hideHeaders columns={columns} - data={sortBy((resourceContent.content as OpenapiDirContent)?.entries || [], ['type', 'name'])} - onRowClick={data => { + data={mergedContentEntries} + onRowClick={entry => { history.push( routes.toCODERepository({ repoPath: repoMetadata.path as string, gitRef, - resourcePath: data.path + resourcePath: entry.path }) ) }} @@ -107,3 +205,92 @@ export function FolderContent({ ) } + +type PathDetails = { + details: Array<{ + path: string + last_commit: TypesCommit + }> +} + +type PathsChunks = Array<{ + paths: string[] + loaded: boolean + loading: boolean + failed: boolean +}> + +interface CommitMessageLinksProps extends Pick { + rowData: OpenapiContentInfo +} + +const CommitMessageLinks: React.FC = ({ repoMetadata, rowData }) => { + const { routes } = useAppContext() + let title: string | JSX.Element = (rowData.latest_commit?.title || '') as string + const match = title.match(/\(#\d+\)$/) + + if (match?.length) { + const titleWithoutPullRequestId = title.replace(match[0], '') + const pullRequestId = match[0].replace('(#', '').replace(')', '') + + title = ( + + ), + PR_URL: ( + + ) + }} + /> + ) + } else { + title = ( + + ) + } + + return ( + + {title} + + ) +} + +interface ListingItemLinkProps extends TextProps { + url: string + text: string + wrapperClassName?: string +} + +const ListingItemLink: React.FC = ({ url, text, className, wrapperClassName, ...props }) => ( + + + + {text.trim()} + + + +) diff --git a/web/src/pages/Repository/RepositoryContent/FolderContent/Readme.tsx b/web/src/pages/Repository/RepositoryContent/FolderContent/Readme.tsx index aeac045cc..ff5f68581 100644 --- a/web/src/pages/Repository/RepositoryContent/FolderContent/Readme.tsx +++ b/web/src/pages/Repository/RepositoryContent/FolderContent/Readme.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react' +import React, { useEffect, useMemo, useRef } from 'react' import { Container, Color, Layout, FlexExpander, ButtonVariation, Heading, Icon, ButtonSize } from '@harness/uicore' import { Render } from 'react-jsx-match' import { useHistory } from 'react-router-dom' @@ -26,21 +26,31 @@ interface FolderContentProps { function ReadmeViewer({ metadata, gitRef, readmeInfo, contentOnly, maxWidth }: FolderContentProps) { const { getString } = useStrings() const history = useHistory() - const { routes } = useAppContext() - - const { data, error, loading } = useGet({ + const { routes, standalone, hooks } = useAppContext() + const space = useGetSpaceParam() + const { data, error, loading, refetch } = useGet({ path: `/api/v1/repos/${metadata.path}/+/content/${readmeInfo?.path}`, queryParams: { include_commit: false, git_ref: gitRef - } + }, + lazy: true }) + const ref = useRef(gitRef as string) useShowRequestError(error) - const { standalone } = useAppContext() - const { hooks } = useAppContext() - const space = useGetSpaceParam() + // Fix an issue where readmeInfo is old (its new data is being fetched) but gitRef is + // changed, causing README fetching to be incorrect. An example of this issue is to have + // two branches, one has README.md and the other has README. If you switch between the two + // branches, the README fetch will be incorrect (404). + useEffect(() => { + if (gitRef === ref.current) { + refetch() + } else { + ref.current = gitRef as string + } + }, [refetch, gitRef]) const permPushResult = hooks?.usePermissionTranslate?.( { diff --git a/web/src/pages/Repository/RepositoryContent/RepositoryContent.module.scss b/web/src/pages/Repository/RepositoryContent/RepositoryContent.module.scss index 1d6870d7a..4ac1e7ef6 100644 --- a/web/src/pages/Repository/RepositoryContent/RepositoryContent.module.scss +++ b/web/src/pages/Repository/RepositoryContent/RepositoryContent.module.scss @@ -1,4 +1,5 @@ .resourceContent { + background-color: var(--page-background) !important; flex-grow: 1; display: flex; flex-direction: column; diff --git a/web/src/pages/Repository/RepositoryContent/RepositoryContent.tsx b/web/src/pages/Repository/RepositoryContent/RepositoryContent.tsx index 02d37e03a..f541b2545 100644 --- a/web/src/pages/Repository/RepositoryContent/RepositoryContent.tsx +++ b/web/src/pages/Repository/RepositoryContent/RepositoryContent.tsx @@ -13,9 +13,7 @@ export function RepositoryContent({ resourceContent, commitRef }: Pick) { - useEffect(() => { - window.scroll({ top: 0 }) - }, [gitRef, resourcePath]) + useEffect(() => window.scroll({ top: 0 }), [gitRef, resourcePath]) return ( diff --git a/web/src/pages/RepositoryBranches/RepositoryBranches.module.scss b/web/src/pages/RepositoryBranches/RepositoryBranches.module.scss index d1b1df197..f1fb64bb6 100644 --- a/web/src/pages/RepositoryBranches/RepositoryBranches.module.scss +++ b/web/src/pages/RepositoryBranches/RepositoryBranches.module.scss @@ -1,4 +1,4 @@ .main { - min-height: var(--page-min-height, 100%); + min-height: var(--page-height); background-color: var(--primary-bg) !important; } diff --git a/web/src/pages/RepositoryCommit/RepositoryCommit.module.scss b/web/src/pages/RepositoryCommit/RepositoryCommit.module.scss index 19844121c..321c05559 100644 --- a/web/src/pages/RepositoryCommit/RepositoryCommit.module.scss +++ b/web/src/pages/RepositoryCommit/RepositoryCommit.module.scss @@ -1,5 +1,5 @@ .main { - min-height: var(--page-min-height, 100%); + min-height: var(--page-height); background-color: var(--primary-bg) !important; } diff --git a/web/src/pages/RepositoryCommit/RepositoryCommit.tsx b/web/src/pages/RepositoryCommit/RepositoryCommit.tsx index 43751fd50..2e93340da 100644 --- a/web/src/pages/RepositoryCommit/RepositoryCommit.tsx +++ b/web/src/pages/RepositoryCommit/RepositoryCommit.tsx @@ -38,8 +38,8 @@ export default function RepositoryCommits() { { + const space = useGetSpaceParam() + const { secret: secretName } = useParams() + + const { + data: secret + // error, + // loading, + // refetch + // response + } = useGet({ + path: `/api/v1/secrets/${space}/${secretName}/+` + }) + + return ( + + + + ) +} + +export default Execution diff --git a/web/src/pages/SecretList/SecretList.module.scss b/web/src/pages/SecretList/SecretList.module.scss index d1b1df197..dfdc0aec4 100644 --- a/web/src/pages/SecretList/SecretList.module.scss +++ b/web/src/pages/SecretList/SecretList.module.scss @@ -1,4 +1,86 @@ .main { - min-height: var(--page-min-height, 100%); + min-height: var(--page-height); background-color: var(--primary-bg) !important; + + .layout { + align-items: center; + } +} + +.withError { + display: grid; +} + +.table { + [class*='TableV2--header'] [class*='variation-table-headers'] { + text-transform: none; + color: var(--grey-400); + font-weight: 500; + font-size: 13px; + } + + .row { + height: 80px; + box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16); + overflow: hidden; + + &.noDesc > div { + height: 44px; + } + } +} + +.nameContainer { + position: relative; + + .name { + flex-grow: 1; + align-items: baseline !important; + width: calc(100% - 100px) !important; + + > span { + width: 100%; + > span { + width: 100%; + } + } + + & + span:last-of-type { + align-self: center; + } + } + + .pinned { + transform: rotate(-90deg); + position: absolute; + top: 7px; + left: -43px; + font-size: var(--font-size-xsmall) !important; + padding: 6px 14px; + } + + .repoName { + font-weight: 600 !important; + font-size: 16px !important; + line-height: 24px !important; + color: var(--grey-800); + + .repoScope { + color: var(--grey-400); + padding: 2px 6px; + font-size: var(--font-size-xsmall) !important; + border-radius: 4px; + border: 1px solid var(--grey-200); + display: inline-block; + margin-left: var(--spacing-medium); + text-transform: uppercase; + line-height: 16px; + } + } + + .desc { + color: var(--grey-500); + font-size: var(--font-size-small); + padding-top: var(--spacing-xsmall) !important; + } } diff --git a/web/src/pages/SecretList/SecretList.module.scss.d.ts b/web/src/pages/SecretList/SecretList.module.scss.d.ts index 9e614bf2d..c83440288 100644 --- a/web/src/pages/SecretList/SecretList.module.scss.d.ts +++ b/web/src/pages/SecretList/SecretList.module.scss.d.ts @@ -2,5 +2,16 @@ // this is an auto-generated file declare const styles: { readonly main: string + readonly layout: string + readonly withError: string + readonly table: string + readonly row: string + readonly noDesc: string + readonly nameContainer: string + readonly name: string + readonly pinned: string + readonly repoName: string + readonly repoScope: string + readonly desc: string } export default styles diff --git a/web/src/pages/SecretList/SecretList.tsx b/web/src/pages/SecretList/SecretList.tsx index da1e9e9e2..fc3cfa5bd 100644 --- a/web/src/pages/SecretList/SecretList.tsx +++ b/web/src/pages/SecretList/SecretList.tsx @@ -1,16 +1,151 @@ -import React from 'react' -import { Container, PageHeader } from '@harness/uicore' +import React, { useMemo, useState } from 'react' +import { + ButtonVariation, + Color, + Container, + FlexExpander, + Layout, + PageBody, + PageHeader, + TableV2 as Table, + Text +} from '@harness/uicore' +import cx from 'classnames' +import type { CellProps, Column } from 'react-table' +import Keywords from 'react-keywords' +import { useHistory } from 'react-router-dom' +import { useGet } from 'restful-react' import { useStrings } from 'framework/strings' +import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' +import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner' +import { NoResultCard } from 'components/NoResultCard/NoResultCard' +import { LIST_FETCHING_LIMIT, PageBrowserProps, formatDate, getErrorMessage, voidFn } from 'utils/Utils' +import type { TypesSecret } from 'services/code' +import { usePageIndex } from 'hooks/usePageIndex' +import { useQueryParams } from 'hooks/useQueryParams' +import { useGetSpaceParam } from 'hooks/useGetSpaceParam' +import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination' +import { NewSecretModalButton } from 'components/NewSecretModalButton/NewSecretModalButton' +import { useAppContext } from 'AppContext' +import noSecretsImage from '../RepositoriesListing/no-repo.svg' import css from './SecretList.module.scss' -const PipelineList = () => { +const SecretList = () => { + const { routes } = useAppContext() + const space = useGetSpaceParam() + const history = useHistory() const { getString } = useStrings() + const [searchTerm, setSearchTerm] = useState() + const pageBrowser = useQueryParams() + const pageInit = pageBrowser.page ? parseInt(pageBrowser.page) : 1 + const [page, setPage] = usePageIndex(pageInit) + + const { + data: secrets, + error, + loading, + refetch, + response + } = useGet({ + path: `/api/v1/spaces/${space}/secrets`, + queryParams: { page, limit: LIST_FETCHING_LIMIT, query: searchTerm } + }) + + const NewSecretButton = ( + + history.push(routes.toCODESecret({ space, secret: secretInfo.uid as string })) + }> + ) + + const columns: Column[] = useMemo( + () => [ + { + Header: getString('secrets.name'), + width: 'calc(100% - 180px)', + Cell: ({ row }: CellProps) => { + const record = row.original + return ( + + + + + {record.uid} + + {record.description && {record.description}} + + + + ) + } + }, + { + Header: getString('repos.updated'), + width: '180px', + Cell: ({ row }: CellProps) => { + return ( + + + {formatDate(row.original.updated as number)} + + + ) + }, + disableSortBy: true + } + ], + [getString, searchTerm] + ) return ( + secrets?.length === 0 && searchTerm === undefined, + image: noSecretsImage, + message: getString('secrets.noData'), + button: NewSecretButton + }}> + + + + + {NewSecretButton} + + + + + + {!!secrets?.length && ( + + className={css.table} + columns={columns} + data={secrets || []} + onRowClick={secretInfo => + history.push(routes.toCODESecret({ space: 'root', secret: secretInfo.uid as string })) + } + getRowClassName={row => cx(css.row, !row.original.description && css.noDesc)} + /> + )} + !!secrets && secrets?.length === 0 && !!searchTerm?.length} + forSearch={true} + /> + + + + ) } -export default PipelineList +export default SecretList diff --git a/web/src/pages/SignIn/SignIn.tsx b/web/src/pages/SignIn/SignIn.tsx index 2dc2cbd05..9bcedb04e 100644 --- a/web/src/pages/SignIn/SignIn.tsx +++ b/web/src/pages/SignIn/SignIn.tsx @@ -6,12 +6,13 @@ import { Link } from 'react-router-dom' import { useStrings } from 'framework/strings' import { useOnLogin } from 'services/code' import AuthLayout from 'components/AuthLayout/AuthLayout' -import { routes } from 'RouteDefinitions' +import { useAppContext } from 'AppContext' import { useAPIToken } from 'hooks/useAPIToken' import { getErrorMessage, type LoginForm } from 'utils/Utils' import css from './SignIn.module.scss' export const SignIn: React.FC = () => { + const { routes } = useAppContext() const { getString } = useStrings() const [, setToken] = useAPIToken() const { mutate } = useOnLogin({}) diff --git a/web/src/pages/SignUp/SignUp.tsx b/web/src/pages/SignUp/SignUp.tsx index 06744ea47..d8218d903 100644 --- a/web/src/pages/SignUp/SignUp.tsx +++ b/web/src/pages/SignUp/SignUp.tsx @@ -13,10 +13,10 @@ import { } from '@harness/uicore' import * as Yup from 'yup' -import { Link, useHistory } from 'react-router-dom' +import { Link } from 'react-router-dom' import { useStrings } from 'framework/strings' import AuthLayout from 'components/AuthLayout/AuthLayout' -import { routes } from 'RouteDefinitions' +import { useAppContext } from 'AppContext' import { getErrorMessage, type RegisterForm } from 'utils/Utils' import { useOnRegister } from 'services/code' import { useAPIToken } from 'hooks/useAPIToken' @@ -24,6 +24,7 @@ import css from './SignUp.module.scss' // Renders the Register page. export const SignUp: React.FC = () => { + const { routes } = useAppContext() const { getString } = useStrings() const { showError, showSuccess } = useToaster() const [, setToken] = useAPIToken() diff --git a/web/src/pages/SpaceAccessControl/AddNewMember/AddNewMember.tsx b/web/src/pages/SpaceAccessControl/AddNewMember/AddNewMember.tsx index 1c20da8f1..3f3eb1b7a 100644 --- a/web/src/pages/SpaceAccessControl/AddNewMember/AddNewMember.tsx +++ b/web/src/pages/SpaceAccessControl/AddNewMember/AddNewMember.tsx @@ -6,12 +6,12 @@ import { Formik } from 'formik' import * as Yup from 'yup' import { useStrings } from 'framework/strings' +import { useGet } from 'restful-react' import { useGetSpaceParam } from 'hooks/useGetSpaceParam' import { MembershipAddRequestBody, - TypesMembership, + TypesMembershipUser, TypesPrincipalInfo, - useListPrincipals, useMembershipAdd, useMembershipUpdate } from 'services/code' @@ -23,7 +23,7 @@ const roles = ['reader', 'executor', 'contributor', 'space_owner'] as const const useAddNewMember = ({ onClose }: { onClose: () => void }) => { const [isEditFlow, setIsEditFlow] = useState(false) - const [membershipDetails, setMembershipDetails] = useState() + const [membershipDetails, setMembershipDetails] = useState() const [searchTerm, setSearchTerm] = useState('') const space = useGetSpaceParam() @@ -36,23 +36,25 @@ const useAddNewMember = ({ onClose }: { onClose: () => void }) => { user_uid: membershipDetails?.principal?.uid || '' }) - const { data: users, loading: fetchingUsers } = useListPrincipals({ + const { data: users, loading: fetchingUsers } = useGet({ + path: `/api/v1/principals`, queryParams: { query: searchTerm, page: 1, limit: LIST_FETCHING_LIMIT, - type: ['user'] + type: 'user' }, debounce: 500 }) + const roleOptions: SelectOption[] = useMemo( () => roles.map(role => ({ value: role, label: getString(roleStringKeyMap[role]) })), - [] + [] // eslint-disable-line react-hooks/exhaustive-deps ) const userOptions: SelectOption[] = useMemo( diff --git a/web/src/pages/SpaceAccessControl/SpaceAccessControl.module.scss b/web/src/pages/SpaceAccessControl/SpaceAccessControl.module.scss index 8caa9ba8d..9842f74ef 100644 --- a/web/src/pages/SpaceAccessControl/SpaceAccessControl.module.scss +++ b/web/src/pages/SpaceAccessControl/SpaceAccessControl.module.scss @@ -1,5 +1,5 @@ .mainCtn { - height: var(--page-min-height, 100%); + height: var(--page-height); background-color: var(--primary-bg) !important; .roleBadge { diff --git a/web/src/pages/SpaceAccessControl/SpaceAccessControl.tsx b/web/src/pages/SpaceAccessControl/SpaceAccessControl.tsx index 2f73145b2..9371118d9 100644 --- a/web/src/pages/SpaceAccessControl/SpaceAccessControl.tsx +++ b/web/src/pages/SpaceAccessControl/SpaceAccessControl.tsx @@ -6,7 +6,7 @@ import type { CellProps, Column } from 'react-table' import { StringKeys, useStrings } from 'framework/strings' import { useConfirmAct } from 'hooks/useConfirmAction' import { useGetSpaceParam } from 'hooks/useGetSpaceParam' -import { EnumMembershipRole, TypesMembership, useMembershipDelete, useMembershipList } from 'services/code' +import { EnumMembershipRole, TypesMembershipUser, useMembershipDelete, useMembershipList } from 'services/code' import { getErrorMessage } from 'utils/Utils' import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton' @@ -60,7 +60,7 @@ const SpaceAccessControl = () => { { Header: getString('user'), width: '30%', - Cell: ({ row }: CellProps) => ( + Cell: ({ row }: CellProps) => ( { { Header: getString('role'), width: '40%', - Cell: ({ row }: CellProps) => { + Cell: ({ row }: CellProps) => { const stringKey = row.original.role ? roleStringKeyMap[row.original.role] : undefined return ( @@ -91,7 +91,7 @@ const SpaceAccessControl = () => { { Header: getString('email'), width: '25%', - Cell: ({ row }: CellProps) => ( + Cell: ({ row }: CellProps) => ( {row.original.principal?.email} @@ -100,7 +100,7 @@ const SpaceAccessControl = () => { { accessor: 'action', width: '5%', - Cell: ({ row }: CellProps) => { + Cell: ({ row }: CellProps) => { return ( { ) } } - ] as Column[], - [] + ] as Column[], + [] // eslint-disable-line react-hooks/exhaustive-deps ) return ( diff --git a/web/src/pages/SpaceSettings/SpaceSettings.module.scss b/web/src/pages/SpaceSettings/SpaceSettings.module.scss index 5a485e353..12c3f3ba8 100644 --- a/web/src/pages/SpaceSettings/SpaceSettings.module.scss +++ b/web/src/pages/SpaceSettings/SpaceSettings.module.scss @@ -1,5 +1,5 @@ .mainCtn { - height: var(--page-min-height, 100%); + height: var(--page-height); background-color: var(--primary-bg) !important; .roleBadge { @@ -59,4 +59,4 @@ .verticalContainer { width: 100% !important; -} \ No newline at end of file +} diff --git a/web/src/pages/SpaceSettings/SpaceSettings.tsx b/web/src/pages/SpaceSettings/SpaceSettings.tsx index 6040d9690..e67e64124 100644 --- a/web/src/pages/SpaceSettings/SpaceSettings.tsx +++ b/web/src/pages/SpaceSettings/SpaceSettings.tsx @@ -39,7 +39,9 @@ export default function SpaceSettings() { name: data?.uid, desc: data?.description }} - onSubmit={voidFn(() => {})}> + onSubmit={voidFn(() => { + // @typescript-eslint/no-empty-function + })}> {formik => { return ( diff --git a/web/src/pages/UserProfile/UserProfile.module.scss b/web/src/pages/UserProfile/UserProfile.module.scss index 4fa665ae8..9d33fbcec 100644 --- a/web/src/pages/UserProfile/UserProfile.module.scss +++ b/web/src/pages/UserProfile/UserProfile.module.scss @@ -1,5 +1,5 @@ .mainCtn { - height: var(--page-min-height, 100%); + height: var(--page-height); background-color: var(--primary-bg) !important; .pageCtn { diff --git a/web/src/pages/UserProfile/UserProfile.tsx b/web/src/pages/UserProfile/UserProfile.tsx index 849149120..b78d21eb1 100644 --- a/web/src/pages/UserProfile/UserProfile.tsx +++ b/web/src/pages/UserProfile/UserProfile.tsx @@ -21,10 +21,10 @@ import moment from 'moment' import { useStrings } from 'framework/strings' import { useAPIToken } from 'hooks/useAPIToken' -import { routes } from 'RouteDefinitions' import { TypesToken, TypesUser, useGetUser, useOpLogout, useUpdateUser } from 'services/code' import { ButtonRoleProps, getErrorMessage } from 'utils/Utils' import { useConfirmAct } from 'hooks/useConfirmAction' +import { useAppContext } from 'AppContext' import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton' @@ -39,6 +39,7 @@ const UserProfile = () => { const history = useHistory() const { getString } = useStrings() const { showSuccess, showError } = useToaster() + const { routes } = useAppContext() const { data: currentUser, loading: currentUserLoading, refetch: refetchCurrentUser } = useGetUser({}) const { mutate: updateUser } = useUpdateUser({}) @@ -146,7 +147,7 @@ const UserProfile = () => { } } ], - [] + [] // eslint-disable-line react-hooks/exhaustive-deps ) const onEditField = async (field: keyof TypesUser, value: string) => { diff --git a/web/src/pages/UsersListing/UsersListing.module.scss b/web/src/pages/UsersListing/UsersListing.module.scss index aff390be3..f14ba353f 100644 --- a/web/src/pages/UsersListing/UsersListing.module.scss +++ b/web/src/pages/UsersListing/UsersListing.module.scss @@ -1,5 +1,5 @@ .mainCtn { - height: var(--page-min-height, 100%); + height: var(--page-height); background-color: var(--primary-bg) !important; .adminBadge { diff --git a/web/src/pages/WebhookNew/WehookForm.module.scss b/web/src/pages/WebhookNew/WehookForm.module.scss index d7ebc7701..fd28c93ed 100644 --- a/web/src/pages/WebhookNew/WehookForm.module.scss +++ b/web/src/pages/WebhookNew/WehookForm.module.scss @@ -1,5 +1,5 @@ .main { - min-height: var(--page-min-height, 100%); + min-height: var(--page-height); background-color: var(--primary-bg) !important; } diff --git a/web/src/pages/Webhooks/Webhooks.module.scss b/web/src/pages/Webhooks/Webhooks.module.scss index 5e82897f8..75244011d 100644 --- a/web/src/pages/Webhooks/Webhooks.module.scss +++ b/web/src/pages/Webhooks/Webhooks.module.scss @@ -1,5 +1,5 @@ .main { - min-height: var(--page-min-height, 100%); + min-height: var(--page-height); background-color: var(--primary-bg) !important; .table { diff --git a/web/src/services/code/index.tsx b/web/src/services/code/index.tsx index e9ba2d77c..b7e567a20 100644 --- a/web/src/services/code/index.tsx +++ b/web/src/services/code/index.tsx @@ -7,7 +7,7 @@ import { getConfig } from '../config' export const SPEC_VERSION = '0.0.0' export type EnumAccessGrant = number -export type EnumCheckPayloadKind = 'external' +export type EnumCheckPayloadKind = '' | 'markdown' | 'raw' export type EnumCheckStatus = 'error' | 'failure' | 'pending' | 'running' | 'success' @@ -45,6 +45,8 @@ export type EnumPullReqReviewerType = 'assigned' | 'requested' | 'self_assigned' export type EnumPullReqState = 'closed' | 'merged' | 'open' +export type EnumScmType = 'GITNESS' | 'GITHUB' | 'GITLAB' | 'UNKNOWN' + export type EnumTokenType = string export type EnumWebhookExecutionResult = 'fatal_error' | 'retriable_error' | 'success' | null @@ -67,13 +69,13 @@ export interface GitrpcBlamePart { lines?: string[] | null } -export type GitrpcCommit = { +export interface GitrpcCommit { author?: GitrpcSignature committer?: GitrpcSignature message?: string sha?: string title?: string -} | null +} export type GitrpcFileAction = 'CREATE' | 'UPDATE' | 'DELETE' | 'MOVE' @@ -82,6 +84,12 @@ export interface GitrpcIdentity { name?: string } +export interface GitrpcPathDetails { + last_commit?: GitrpcCommit + path?: string + size?: number +} + export interface GitrpcSignature { identity?: GitrpcIdentity when?: string @@ -147,13 +155,27 @@ export type OpenapiContentType = 'file' | 'dir' | 'symlink' | 'submodule' export interface OpenapiCreateBranchRequest { name?: string - target?: string | null + target?: string +} + +export interface OpenapiCreateExecutionRequest { + status?: string } export interface OpenapiCreatePathRequest { path?: string } +export interface OpenapiCreatePipelineRequest { + config_path?: string + default_branch?: string + description?: string + repo_ref?: string + repo_type?: EnumScmType + space_ref?: string + uid?: string +} + export interface OpenapiCreatePullReqRequest { description?: string is_draft?: boolean @@ -179,6 +201,13 @@ export interface OpenapiCreateRepositoryRequest { uid?: string } +export interface OpenapiCreateSecretRequest { + data?: string + description?: string + space_ref?: string + uid?: string +} + export interface OpenapiCreateSpaceRequest { description?: string is_public?: boolean @@ -187,9 +216,9 @@ export interface OpenapiCreateSpaceRequest { } export interface OpenapiCreateTagRequest { - message?: string | null + message?: string name?: string - target?: string | null + target?: string } export interface OpenapiCreateTokenRequest { @@ -243,6 +272,10 @@ export interface OpenapiMoveSpaceRequest { uid?: string | null } +export interface OpenapiPathsDetailsRequest { + paths?: string[] | null +} + export interface OpenapiRegisterRequest { display_name?: string email?: string @@ -250,14 +283,6 @@ export interface OpenapiRegisterRequest { uid?: string } -export interface OpenapiReportStatusCheckResultRequest { - check_uid?: string - link?: string - payload?: TypesCheckPayload - status?: EnumCheckStatus - summary?: string -} - export interface OpenapiReviewSubmitPullReqRequest { commit_sha?: string decision?: EnumPullReqReviewDecision @@ -278,6 +303,16 @@ export interface OpenapiUpdateAdminRequest { admin?: boolean } +export interface OpenapiUpdateExecutionRequest { + status?: string +} + +export interface OpenapiUpdatePipelineRequest { + config_path?: string + description?: string + uid?: string +} + export interface OpenapiUpdatePullReqRequest { description?: string title?: string @@ -288,6 +323,12 @@ export interface OpenapiUpdateRepoRequest { is_public?: boolean | null } +export interface OpenapiUpdateSecretRequest { + data?: string + description?: string + uid?: string +} + export interface OpenapiUpdateSpaceRequest { description?: string | null is_public?: boolean | null @@ -384,6 +425,10 @@ export interface RepoMergeCheck { mergeable?: boolean } +export interface RepoPathsDetailsOutput { + details?: GitrpcPathDetails[] | null +} + export interface RepoSubmoduleContent { commit_sha?: string url?: string @@ -394,7 +439,7 @@ export interface RepoSymlinkContent { target?: string } -export type TimeDuration = number +export type TimeDuration = number | null export interface TypesCheck { created?: number @@ -439,6 +484,44 @@ export interface TypesDiffStats { files_changed?: number } +export interface TypesExecution { + action?: string + after?: string + author_avatar?: string + author_email?: string + author_login?: string + author_name?: string + before?: string + created?: number + cron?: string + debug?: boolean + deploy_id?: number + deploy_to?: string + error?: string + event?: string + finished?: number + id?: number + link?: string + message?: string + number?: number + params?: string + parent?: number + pipeline_id?: number + ref?: string + repo_id?: number + sender?: string + source?: string + source_repo?: string + started?: number + status?: string + target?: string + timestamp?: number + title?: string + trigger?: string + updated?: number + version?: number +} + export interface TypesIdentity { email?: string name?: string @@ -447,9 +530,18 @@ export interface TypesIdentity { export interface TypesListCommitResponse { commits?: TypesCommit[] | null rename_details?: TypesRenameDetails[] | null + total_commits?: number } -export interface TypesMembership { +export interface TypesMembershipSpace { + added_by?: TypesPrincipalInfo + created?: number + role?: EnumMembershipRole + space?: TypesSpace + updated?: number +} + +export interface TypesMembershipUser { added_by?: TypesPrincipalInfo created?: number principal?: TypesPrincipalInfo @@ -457,6 +549,11 @@ export interface TypesMembership { updated?: number } +export interface TypesMergeResponse { + conflict_files?: string[] + sha?: string +} + export interface TypesPath { created?: number created_by?: number @@ -468,6 +565,22 @@ export interface TypesPath { value?: string } +export interface TypesPipeline { + config_path?: string + created?: number + default_branch?: string + description?: string + id?: number + repo_id?: number + repo_name?: string + repo_type?: EnumScmType + seq?: number + space_id?: number + uid?: string + updated?: number + version?: number +} + export interface TypesPrincipalInfo { created?: number display_name?: string @@ -569,6 +682,16 @@ export interface TypesRepository { updated?: number } +export interface TypesSecret { + created?: number + description?: string + id?: number + space_id?: number + uid?: string + updated?: number + version?: number +} + export interface TypesServiceAccount { admin?: boolean blocked?: boolean @@ -600,7 +723,7 @@ export interface TypesSpace { export interface TypesToken { created_by?: number - expires_at?: number + expires_at?: number | null grants?: EnumAccessGrant issued_at?: number principal_id?: number @@ -854,6 +977,274 @@ export type UseOpLogoutProps = Omit useMutate('POST', `/logout`, { base: getConfig('code'), ...props }) +export type CreatePipelineProps = Omit< + MutateProps, + 'path' | 'verb' +> + +export const CreatePipeline = (props: CreatePipelineProps) => ( + + verb="POST" + path={`/pipelines`} + base={getConfig('code')} + {...props} + /> +) + +export type UseCreatePipelineProps = Omit< + UseMutateProps, + 'path' | 'verb' +> + +export const useCreatePipeline = (props: UseCreatePipelineProps) => + useMutate('POST', `/pipelines`, { + base: getConfig('code'), + ...props + }) + +export type DeletePipelineProps = Omit, 'path' | 'verb'> + +export const DeletePipeline = (props: DeletePipelineProps) => ( + + verb="DELETE" + path={`/pipelines`} + base={getConfig('code')} + {...props} + /> +) + +export type UseDeletePipelineProps = Omit, 'path' | 'verb'> + +export const useDeletePipeline = (props: UseDeletePipelineProps) => + useMutate('DELETE', `/pipelines`, { base: getConfig('code'), ...props }) + +export interface FindPipelinePathParams { + pipeline_ref: string +} + +export type FindPipelineProps = Omit, 'path'> & + FindPipelinePathParams + +export const FindPipeline = ({ pipeline_ref, ...props }: FindPipelineProps) => ( + + path={`/pipelines/${pipeline_ref}`} + base={getConfig('code')} + {...props} + /> +) + +export type UseFindPipelineProps = Omit< + UseGetProps, + 'path' +> & + FindPipelinePathParams + +export const useFindPipeline = ({ pipeline_ref, ...props }: UseFindPipelineProps) => + useGet( + (paramsInPath: FindPipelinePathParams) => `/pipelines/${paramsInPath.pipeline_ref}`, + { base: getConfig('code'), pathParams: { pipeline_ref }, ...props } + ) + +export interface UpdatePipelinePathParams { + pipeline_ref: string +} + +export type UpdatePipelineProps = Omit< + MutateProps, + 'path' | 'verb' +> & + UpdatePipelinePathParams + +export const UpdatePipeline = ({ pipeline_ref, ...props }: UpdatePipelineProps) => ( + + verb="PATCH" + path={`/pipelines/${pipeline_ref}`} + base={getConfig('code')} + {...props} + /> +) + +export type UseUpdatePipelineProps = Omit< + UseMutateProps, + 'path' | 'verb' +> & + UpdatePipelinePathParams + +export const useUpdatePipeline = ({ pipeline_ref, ...props }: UseUpdatePipelineProps) => + useMutate( + 'PATCH', + (paramsInPath: UpdatePipelinePathParams) => `/pipelines/${paramsInPath.pipeline_ref}`, + { base: getConfig('code'), pathParams: { pipeline_ref }, ...props } + ) + +export interface ListExecutionsQueryParams { + /** + * The page to return. + */ + page?: number + /** + * The maximum number of results to return. + */ + limit?: number +} + +export interface ListExecutionsPathParams { + pipeline_ref: string +} + +export type ListExecutionsProps = Omit< + GetProps, + 'path' +> & + ListExecutionsPathParams + +export const ListExecutions = ({ pipeline_ref, ...props }: ListExecutionsProps) => ( + + path={`/pipelines/${pipeline_ref}/executions`} + base={getConfig('code')} + {...props} + /> +) + +export type UseListExecutionsProps = Omit< + UseGetProps, + 'path' +> & + ListExecutionsPathParams + +export const useListExecutions = ({ pipeline_ref, ...props }: UseListExecutionsProps) => + useGet( + (paramsInPath: ListExecutionsPathParams) => `/pipelines/${paramsInPath.pipeline_ref}/executions`, + { base: getConfig('code'), pathParams: { pipeline_ref }, ...props } + ) + +export interface CreateExecutionPathParams { + pipeline_ref: string +} + +export type CreateExecutionProps = Omit< + MutateProps, + 'path' | 'verb' +> & + CreateExecutionPathParams + +export const CreateExecution = ({ pipeline_ref, ...props }: CreateExecutionProps) => ( + + verb="POST" + path={`/pipelines/${pipeline_ref}/executions`} + base={getConfig('code')} + {...props} + /> +) + +export type UseCreateExecutionProps = Omit< + UseMutateProps, + 'path' | 'verb' +> & + CreateExecutionPathParams + +export const useCreateExecution = ({ pipeline_ref, ...props }: UseCreateExecutionProps) => + useMutate( + 'POST', + (paramsInPath: CreateExecutionPathParams) => `/pipelines/${paramsInPath.pipeline_ref}/executions`, + { base: getConfig('code'), pathParams: { pipeline_ref }, ...props } + ) + +export interface DeleteExecutionPathParams { + pipeline_ref: string +} + +export type DeleteExecutionProps = Omit< + MutateProps, + 'path' | 'verb' +> & + DeleteExecutionPathParams + +export const DeleteExecution = ({ pipeline_ref, ...props }: DeleteExecutionProps) => ( + + verb="DELETE" + path={`/pipelines/${pipeline_ref}/executions`} + base={getConfig('code')} + {...props} + /> +) + +export type UseDeleteExecutionProps = Omit< + UseMutateProps, + 'path' | 'verb' +> & + DeleteExecutionPathParams + +export const useDeleteExecution = ({ pipeline_ref, ...props }: UseDeleteExecutionProps) => + useMutate( + 'DELETE', + (paramsInPath: DeleteExecutionPathParams) => `/pipelines/${paramsInPath.pipeline_ref}/executions`, + { base: getConfig('code'), pathParams: { pipeline_ref }, ...props } + ) + +export interface FindExecutionPathParams { + pipeline_ref: string + execution_number: string +} + +export type FindExecutionProps = Omit, 'path'> & + FindExecutionPathParams + +export const FindExecution = ({ pipeline_ref, execution_number, ...props }: FindExecutionProps) => ( + + path={`/pipelines/${pipeline_ref}/executions/${execution_number}`} + base={getConfig('code')} + {...props} + /> +) + +export type UseFindExecutionProps = Omit< + UseGetProps, + 'path' +> & + FindExecutionPathParams + +export const useFindExecution = ({ pipeline_ref, execution_number, ...props }: UseFindExecutionProps) => + useGet( + (paramsInPath: FindExecutionPathParams) => + `/pipelines/${paramsInPath.pipeline_ref}/executions/${paramsInPath.execution_number}`, + { base: getConfig('code'), pathParams: { pipeline_ref, execution_number }, ...props } + ) + +export interface UpdateExecutionPathParams { + pipeline_ref: string + execution_number: string +} + +export type UpdateExecutionProps = Omit< + MutateProps, + 'path' | 'verb' +> & + UpdateExecutionPathParams + +export const UpdateExecution = ({ pipeline_ref, execution_number, ...props }: UpdateExecutionProps) => ( + + verb="PATCH" + path={`/pipelines/${pipeline_ref}/executions/${execution_number}`} + base={getConfig('code')} + {...props} + /> +) + +export type UseUpdateExecutionProps = Omit< + UseMutateProps, + 'path' | 'verb' +> & + UpdateExecutionPathParams + +export const useUpdateExecution = ({ pipeline_ref, execution_number, ...props }: UseUpdateExecutionProps) => + useMutate( + 'PATCH', + (paramsInPath: UpdateExecutionPathParams) => + `/pipelines/${paramsInPath.pipeline_ref}/executions/${paramsInPath.execution_number}`, + { base: getConfig('code'), pathParams: { pipeline_ref, execution_number }, ...props } + ) + export interface ListPrincipalsQueryParams { /** * The substring by which the principals are filtered. @@ -1224,19 +1615,30 @@ export const useGetBranch = ({ repo_ref, branch_name, ...props }: UseGetBranchPr { base: getConfig('code'), pathParams: { repo_ref, branch_name }, ...props } ) +export interface ListStatusCheckResultsQueryParams { + /** + * The page to return. + */ + page?: number + /** + * The maximum number of results to return. + */ + limit?: number +} + export interface ListStatusCheckResultsPathParams { repo_ref: string commit_sha: string } export type ListStatusCheckResultsProps = Omit< - GetProps, + GetProps, 'path' > & ListStatusCheckResultsPathParams export const ListStatusCheckResults = ({ repo_ref, commit_sha, ...props }: ListStatusCheckResultsProps) => ( - + path={`/repos/${repo_ref}/checks/commits/${commit_sha}`} base={getConfig('code')} {...props} @@ -1244,13 +1646,13 @@ export const ListStatusCheckResults = ({ repo_ref, commit_sha, ...props }: ListS ) export type UseListStatusCheckResultsProps = Omit< - UseGetProps, + UseGetProps, 'path' > & ListStatusCheckResultsPathParams export const useListStatusCheckResults = ({ repo_ref, commit_sha, ...props }: UseListStatusCheckResultsProps) => - useGet( + useGet( (paramsInPath: ListStatusCheckResultsPathParams) => `/repos/${paramsInPath.repo_ref}/checks/commits/${paramsInPath.commit_sha}`, { base: getConfig('code'), pathParams: { repo_ref, commit_sha }, ...props } @@ -1261,12 +1663,20 @@ export interface ReportStatusCheckResultsPathParams { commit_sha: string } +export interface ReportStatusCheckResultsRequestBody { + check_uid?: string + link?: string + payload?: TypesCheckPayload + status?: EnumCheckStatus + summary?: string +} + export type ReportStatusCheckResultsProps = Omit< MutateProps< TypesCheck, UsererrorError, void, - OpenapiReportStatusCheckResultRequest, + ReportStatusCheckResultsRequestBody, ReportStatusCheckResultsPathParams >, 'path' | 'verb' @@ -1274,7 +1684,7 @@ export type ReportStatusCheckResultsProps = Omit< ReportStatusCheckResultsPathParams export const ReportStatusCheckResults = ({ repo_ref, commit_sha, ...props }: ReportStatusCheckResultsProps) => ( - + verb="PUT" path={`/repos/${repo_ref}/checks/commits/${commit_sha}`} base={getConfig('code')} @@ -1287,7 +1697,7 @@ export type UseReportStatusCheckResultsProps = Omit< TypesCheck, UsererrorError, void, - OpenapiReportStatusCheckResultRequest, + ReportStatusCheckResultsRequestBody, ReportStatusCheckResultsPathParams >, 'path' | 'verb' @@ -1295,13 +1705,7 @@ export type UseReportStatusCheckResultsProps = Omit< ReportStatusCheckResultsPathParams export const useReportStatusCheckResults = ({ repo_ref, commit_sha, ...props }: UseReportStatusCheckResultsProps) => - useMutate< - TypesCheck, - UsererrorError, - void, - OpenapiReportStatusCheckResultRequest, - ReportStatusCheckResultsPathParams - >( + useMutate( 'PUT', (paramsInPath: ReportStatusCheckResultsPathParams) => `/repos/${paramsInPath.repo_ref}/checks/commits/${paramsInPath.commit_sha}`, @@ -1487,30 +1891,6 @@ export const useCalculateCommitDivergence = ({ repo_ref, ...props }: UseCalculat { base: getConfig('code'), pathParams: { repo_ref }, ...props } ) -export interface RawDiffPathParams { - repo_ref: string - range: string -} - -export type RawDiffProps = Omit, 'path'> & RawDiffPathParams - -export const RawDiff = ({ repo_ref, range, ...props }: RawDiffProps) => ( - - path={`/repos/${repo_ref}/compare/${range}`} - base={getConfig('code')} - {...props} - /> -) - -export type UseRawDiffProps = Omit, 'path'> & - RawDiffPathParams - -export const useRawDiff = ({ repo_ref, range, ...props }: UseRawDiffProps) => - useGet( - (paramsInPath: RawDiffPathParams) => `/repos/${paramsInPath.repo_ref}/compare/${paramsInPath.range}`, - { base: getConfig('code'), pathParams: { repo_ref, range }, ...props } - ) - export interface GetContentQueryParams { /** * The git reference (branch / tag / commitID) that will be used to retrieve the data. If no value is provided the default branch of the repository is used. @@ -1553,28 +1933,28 @@ export const useGetContent = ({ repo_ref, path, ...props }: UseGetContentProps) { base: getConfig('code'), pathParams: { repo_ref, path }, ...props } ) -export interface DiffStatsPathParams { +export interface RawDiffPathParams { repo_ref: string range: string } -export type DiffStatsProps = Omit, 'path'> & - DiffStatsPathParams +export type RawDiffProps = Omit, 'path'> & + RawDiffPathParams -export const DiffStats = ({ repo_ref, range, ...props }: DiffStatsProps) => ( - - path={`/repos/${repo_ref}/diff-stats/${range}`} +export const RawDiff = ({ repo_ref, range, ...props }: RawDiffProps) => ( + + path={`/repos/${repo_ref}/diff/${range}`} base={getConfig('code')} {...props} /> ) -export type UseDiffStatsProps = Omit, 'path'> & - DiffStatsPathParams +export type UseRawDiffProps = Omit, 'path'> & + RawDiffPathParams -export const useDiffStats = ({ repo_ref, range, ...props }: UseDiffStatsProps) => - useGet( - (paramsInPath: DiffStatsPathParams) => `/repos/${paramsInPath.repo_ref}/diff-stats/${paramsInPath.range}`, +export const useRawDiff = ({ repo_ref, range, ...props }: UseRawDiffProps) => + useGet( + (paramsInPath: RawDiffPathParams) => `/repos/${paramsInPath.repo_ref}/diff/${paramsInPath.range}`, { base: getConfig('code'), pathParams: { repo_ref, range }, ...props } ) @@ -1643,6 +2023,69 @@ export const useMoveRepository = ({ repo_ref, ...props }: UseMoveRepositoryProps { base: getConfig('code'), pathParams: { repo_ref }, ...props } ) +export interface PathDetailsQueryParams { + /** + * The git reference (branch / tag / commitID) that will be used to retrieve the data. If no value is provided the default branch of the repository is used. + */ + git_ref?: string +} + +export interface PathDetailsPathParams { + repo_ref: string +} + +export type PathDetailsProps = Omit< + MutateProps< + RepoPathsDetailsOutput, + UsererrorError, + PathDetailsQueryParams, + OpenapiPathsDetailsRequest, + PathDetailsPathParams + >, + 'path' | 'verb' +> & + PathDetailsPathParams + +export const PathDetails = ({ repo_ref, ...props }: PathDetailsProps) => ( + + verb="POST" + path={`/repos/${repo_ref}/path-details`} + base={getConfig('code')} + {...props} + /> +) + +export type UsePathDetailsProps = Omit< + UseMutateProps< + RepoPathsDetailsOutput, + UsererrorError, + PathDetailsQueryParams, + OpenapiPathsDetailsRequest, + PathDetailsPathParams + >, + 'path' | 'verb' +> & + PathDetailsPathParams + +export const usePathDetails = ({ repo_ref, ...props }: UsePathDetailsProps) => + useMutate< + RepoPathsDetailsOutput, + UsererrorError, + PathDetailsQueryParams, + OpenapiPathsDetailsRequest, + PathDetailsPathParams + >('POST', (paramsInPath: PathDetailsPathParams) => `/repos/${paramsInPath.repo_ref}/path-details`, { + base: getConfig('code'), + pathParams: { repo_ref }, + ...props + }) + export interface ListRepositoryPathsQueryParams { /** * The page to return. @@ -2253,45 +2696,19 @@ export const useListPullReqCommits = ({ repo_ref, pullreq_number, ...props }: Us { base: getConfig('code'), pathParams: { repo_ref, pullreq_number }, ...props } ) -export interface RawPullReqDiffPathParams { - repo_ref: string - pullreq_number: number -} - -export type RawPullReqDiffProps = Omit, 'path'> & - RawPullReqDiffPathParams - -export const RawPullReqDiff = ({ repo_ref, pullreq_number, ...props }: RawPullReqDiffProps) => ( - - path={`/repos/${repo_ref}/pullreq/${pullreq_number}/diff`} - base={getConfig('code')} - {...props} - /> -) - -export type UseRawPullReqDiffProps = Omit, 'path'> & - RawPullReqDiffPathParams - -export const useRawPullReqDiff = ({ repo_ref, pullreq_number, ...props }: UseRawPullReqDiffProps) => - useGet( - (paramsInPath: RawPullReqDiffPathParams) => - `/repos/${paramsInPath.repo_ref}/pullreq/${paramsInPath.pullreq_number}/diff`, - { base: getConfig('code'), pathParams: { repo_ref, pullreq_number }, ...props } - ) - export interface MergePullReqOpPathParams { repo_ref: string pullreq_number: number } export type MergePullReqOpProps = Omit< - MutateProps, + MutateProps, 'path' | 'verb' > & MergePullReqOpPathParams export const MergePullReqOp = ({ repo_ref, pullreq_number, ...props }: MergePullReqOpProps) => ( - + verb="POST" path={`/repos/${repo_ref}/pullreq/${pullreq_number}/merge`} base={getConfig('code')} @@ -2300,13 +2717,13 @@ export const MergePullReqOp = ({ repo_ref, pullreq_number, ...props }: MergePull ) export type UseMergePullReqOpProps = Omit< - UseMutateProps, + UseMutateProps, 'path' | 'verb' > & MergePullReqOpPathParams export const useMergePullReqOp = ({ repo_ref, pullreq_number, ...props }: UseMergePullReqOpProps) => - useMutate( + useMutate( 'POST', (paramsInPath: MergePullReqOpPathParams) => `/repos/${paramsInPath.repo_ref}/pullreq/${paramsInPath.pullreq_number}/merge`, @@ -3020,6 +3437,103 @@ export type UseListLicensesProps = Omit useGet(`/resources/license`, { base: getConfig('code'), ...props }) +export type CreateSecretProps = Omit< + MutateProps, + 'path' | 'verb' +> + +export const CreateSecret = (props: CreateSecretProps) => ( + + verb="POST" + path={`/secrets`} + base={getConfig('code')} + {...props} + /> +) + +export type UseCreateSecretProps = Omit< + UseMutateProps, + 'path' | 'verb' +> + +export const useCreateSecret = (props: UseCreateSecretProps) => + useMutate('POST', `/secrets`, { + base: getConfig('code'), + ...props + }) + +export type DeleteSecretProps = Omit, 'path' | 'verb'> + +export const DeleteSecret = (props: DeleteSecretProps) => ( + + verb="DELETE" + path={`/secrets`} + base={getConfig('code')} + {...props} + /> +) + +export type UseDeleteSecretProps = Omit, 'path' | 'verb'> + +export const useDeleteSecret = (props: UseDeleteSecretProps) => + useMutate('DELETE', `/secrets`, { base: getConfig('code'), ...props }) + +export interface FindSecretPathParams { + secret_ref: string +} + +export type FindSecretProps = Omit, 'path'> & + FindSecretPathParams + +export const FindSecret = ({ secret_ref, ...props }: FindSecretProps) => ( + + path={`/secrets/${secret_ref}`} + base={getConfig('code')} + {...props} + /> +) + +export type UseFindSecretProps = Omit, 'path'> & + FindSecretPathParams + +export const useFindSecret = ({ secret_ref, ...props }: UseFindSecretProps) => + useGet( + (paramsInPath: FindSecretPathParams) => `/secrets/${paramsInPath.secret_ref}`, + { base: getConfig('code'), pathParams: { secret_ref }, ...props } + ) + +export interface UpdateSecretPathParams { + secret_ref: string +} + +export type UpdateSecretProps = Omit< + MutateProps, + 'path' | 'verb' +> & + UpdateSecretPathParams + +export const UpdateSecret = ({ secret_ref, ...props }: UpdateSecretProps) => ( + + verb="PATCH" + path={`/secrets/${secret_ref}`} + base={getConfig('code')} + {...props} + /> +) + +export type UseUpdateSecretProps = Omit< + UseMutateProps, + 'path' | 'verb' +> & + UpdateSecretPathParams + +export const useUpdateSecret = ({ secret_ref, ...props }: UseUpdateSecretProps) => + useMutate( + 'PATCH', + (paramsInPath: UpdateSecretPathParams) => `/secrets/${paramsInPath.secret_ref}`, + { base: getConfig('code'), pathParams: { secret_ref }, ...props } + ) + export type CreateSpaceProps = Omit< MutateProps, 'path' | 'verb' @@ -3117,18 +3631,41 @@ export const useUpdateSpace = ({ space_ref, ...props }: UseUpdateSpaceProps) => { base: getConfig('code'), pathParams: { space_ref }, ...props } ) +export interface MembershipListQueryParams { + /** + * The substring by which the space members are filtered. + */ + query?: string + /** + * The order of the output. + */ + order?: 'asc' | 'desc' + /** + * The field by which the space members are sorted. + */ + sort?: 'created' | 'name' + /** + * The page to return. + */ + page?: number + /** + * The maximum number of results to return. + */ + limit?: number +} + export interface MembershipListPathParams { space_ref: string } export type MembershipListProps = Omit< - GetProps, + GetProps, 'path' > & MembershipListPathParams export const MembershipList = ({ space_ref, ...props }: MembershipListProps) => ( - + path={`/spaces/${space_ref}/members`} base={getConfig('code')} {...props} @@ -3136,13 +3673,13 @@ export const MembershipList = ({ space_ref, ...props }: MembershipListProps) => ) export type UseMembershipListProps = Omit< - UseGetProps, + UseGetProps, 'path' > & MembershipListPathParams export const useMembershipList = ({ space_ref, ...props }: UseMembershipListProps) => - useGet( + useGet( (paramsInPath: MembershipListPathParams) => `/spaces/${paramsInPath.space_ref}/members`, { base: getConfig('code'), pathParams: { space_ref }, ...props } ) @@ -3157,13 +3694,13 @@ export interface MembershipAddRequestBody { } export type MembershipAddProps = Omit< - MutateProps, + MutateProps, 'path' | 'verb' > & MembershipAddPathParams export const MembershipAdd = ({ space_ref, ...props }: MembershipAddProps) => ( - + verb="POST" path={`/spaces/${space_ref}/members`} base={getConfig('code')} @@ -3172,13 +3709,13 @@ export const MembershipAdd = ({ space_ref, ...props }: MembershipAddProps) => ( ) export type UseMembershipAddProps = Omit< - UseMutateProps, + UseMutateProps, 'path' | 'verb' > & MembershipAddPathParams export const useMembershipAdd = ({ space_ref, ...props }: UseMembershipAddProps) => - useMutate( + useMutate( 'POST', (paramsInPath: MembershipAddPathParams) => `/spaces/${paramsInPath.space_ref}/members`, { base: getConfig('code'), pathParams: { space_ref }, ...props } @@ -3226,13 +3763,13 @@ export interface MembershipUpdateRequestBody { } export type MembershipUpdateProps = Omit< - MutateProps, + MutateProps, 'path' | 'verb' > & MembershipUpdatePathParams export const MembershipUpdate = ({ space_ref, user_uid, ...props }: MembershipUpdateProps) => ( - + verb="PATCH" path={`/spaces/${space_ref}/members/${user_uid}`} base={getConfig('code')} @@ -3241,13 +3778,13 @@ export const MembershipUpdate = ({ space_ref, user_uid, ...props }: MembershipUp ) export type UseMembershipUpdateProps = Omit< - UseMutateProps, + UseMutateProps, 'path' | 'verb' > & MembershipUpdatePathParams export const useMembershipUpdate = ({ space_ref, user_uid, ...props }: UseMembershipUpdateProps) => - useMutate( + useMutate( 'PATCH', (paramsInPath: MembershipUpdatePathParams) => `/spaces/${paramsInPath.space_ref}/members/${paramsInPath.user_uid}`, { base: getConfig('code'), pathParams: { space_ref, user_uid }, ...props } @@ -3390,6 +3927,51 @@ export const useDeletePath = ({ space_ref, ...props }: UseDeletePathProps) => { base: getConfig('code'), pathParams: { space_ref }, ...props } ) +export interface ListPipelinesQueryParams { + /** + * The substring which is used to filter the repositories by their path name. + */ + query?: string + /** + * The page to return. + */ + page?: number + /** + * The maximum number of results to return. + */ + limit?: number +} + +export interface ListPipelinesPathParams { + space_ref: string +} + +export type ListPipelinesProps = Omit< + GetProps, + 'path' +> & + ListPipelinesPathParams + +export const ListPipelines = ({ space_ref, ...props }: ListPipelinesProps) => ( + + path={`/spaces/${space_ref}/pipelines`} + base={getConfig('code')} + {...props} + /> +) + +export type UseListPipelinesProps = Omit< + UseGetProps, + 'path' +> & + ListPipelinesPathParams + +export const useListPipelines = ({ space_ref, ...props }: UseListPipelinesProps) => + useGet( + (paramsInPath: ListPipelinesPathParams) => `/spaces/${paramsInPath.space_ref}/pipelines`, + { base: getConfig('code'), pathParams: { space_ref }, ...props } + ) + export interface ListReposQueryParams { /** * The substring which is used to filter the repositories by their path name. @@ -3443,6 +4025,51 @@ export const useListRepos = ({ space_ref, ...props }: UseListReposProps) => { base: getConfig('code'), pathParams: { space_ref }, ...props } ) +export interface ListSecretsQueryParams { + /** + * The substring which is used to filter the repositories by their path name. + */ + query?: string + /** + * The page to return. + */ + page?: number + /** + * The maximum number of results to return. + */ + limit?: number +} + +export interface ListSecretsPathParams { + space_ref: string +} + +export type ListSecretsProps = Omit< + GetProps, + 'path' +> & + ListSecretsPathParams + +export const ListSecrets = ({ space_ref, ...props }: ListSecretsProps) => ( + + path={`/spaces/${space_ref}/secrets`} + base={getConfig('code')} + {...props} + /> +) + +export type UseListSecretsProps = Omit< + UseGetProps, + 'path' +> & + ListSecretsPathParams + +export const useListSecrets = ({ space_ref, ...props }: UseListSecretsProps) => + useGet( + (paramsInPath: ListSecretsPathParams) => `/spaces/${paramsInPath.space_ref}/secrets`, + { base: getConfig('code'), pathParams: { space_ref }, ...props } + ) + export interface ListServiceAccountsPathParams { space_ref: string } @@ -3559,6 +4186,21 @@ export const useUpdateUser = (props: UseUpdateUserProps) => ...props }) +export type MembershipSpacesProps = Omit, 'path'> + +export const MembershipSpaces = (props: MembershipSpacesProps) => ( + + path={`/user/memberships`} + base={getConfig('code')} + {...props} + /> +) + +export type UseMembershipSpacesProps = Omit, 'path'> + +export const useMembershipSpaces = (props: UseMembershipSpacesProps) => + useGet(`/user/memberships`, { base: getConfig('code'), ...props }) + export type CreateTokenProps = Omit< MutateProps, 'path' | 'verb' diff --git a/web/src/services/code/swagger.yaml b/web/src/services/code/swagger.yaml index aed4b9471..9f58be95f 100644 --- a/web/src/services/code/swagger.yaml +++ b/web/src/services/code/swagger.yaml @@ -3,42 +3,42 @@ info: title: API Specification version: 0.0.0 servers: - - url: /api/v1/ +- url: /api/v1 security: - - bearerAuth: [] +- bearerAuth: [] paths: /admin/users: get: operationId: adminListUsers parameters: - - in: query - name: sort - schema: - enum: - - id - - email - - created - - updated - type: string - - in: query - name: order - schema: - enum: - - asc - - desc - type: string - - in: query - name: page - schema: - default: 1 - type: integer - - in: query - name: limit - schema: - default: 30 - type: integer + - in: query + name: sort + schema: + enum: + - id + - email + - created + - updated + type: string + - in: query + name: order + schema: + enum: + - asc + - desc + type: string + - in: query + name: page + schema: + default: 1 + type: integer + - in: query + name: limit + schema: + default: 30 + type: integer responses: - '200': + "200": content: application/json: schema: @@ -46,26 +46,26 @@ paths: $ref: '#/components/schemas/TypesUser' type: array description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - admin + - admin post: operationId: adminCreateUser requestBody: @@ -74,168 +74,168 @@ paths: schema: $ref: '#/components/schemas/OpenapiAdminUsersCreateRequest' responses: - '201': + "201": content: application/json: schema: $ref: '#/components/schemas/TypesUser' description: Created - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - admin + - admin /admin/users/{user_uid}: delete: operationId: adminDeleteUser parameters: - - in: path - name: user_uid - required: true - schema: - type: string + - in: path + name: user_uid + required: true + schema: + type: string responses: - '204': + "204": description: No Content - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - admin + - admin get: operationId: adminGetUser parameters: - - in: path - name: user_uid - required: true - schema: - type: string + - in: path + name: user_uid + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesUser' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - admin + - admin patch: operationId: adminUpdateUser parameters: - - in: path - name: user_uid - required: true - schema: - type: string + - in: path + name: user_uid + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiAdminUsersUpdateRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesUser' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - admin + - admin /admin/users/{user_uid}/admin: patch: operationId: updateUserAdmin parameters: - - in: path - name: user_uid - required: true - schema: - type: string + - in: path + name: user_uid + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiUpdateAdminRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesUser' description: OK - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - admin + - admin /login: post: operationId: onLogin @@ -245,106 +245,531 @@ paths: schema: $ref: '#/components/schemas/OpenapiLoginRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesTokenResponse' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - account + - account /logout: post: operationId: opLogout responses: - '200': + "200": description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - account + - account + /pipelines: + post: + operationId: createPipeline + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpenapiCreatePipelineRequest' + responses: + "201": + content: + application/json: + schema: + $ref: '#/components/schemas/TypesPipeline' + description: Created + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - pipeline + /pipelines/{pipeline_ref}: + delete: + operationId: deletePipeline + parameters: + - in: path + name: pipeline_ref + required: true + schema: + type: string + responses: + "204": + description: No Content + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - pipeline + get: + operationId: findPipeline + parameters: + - in: path + name: pipeline_ref + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/TypesPipeline' + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - pipeline + patch: + operationId: updatePipeline + parameters: + - in: path + name: pipeline_ref + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpenapiUpdatePipelineRequest' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/TypesPipeline' + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - pipeline + /pipelines/{pipeline_ref}/executions: + get: + operationId: listExecutions + parameters: + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: pipeline_ref + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/TypesExecution' + type: array + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - pipeline + post: + operationId: createExecution + parameters: + - in: path + name: pipeline_ref + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpenapiCreateExecutionRequest' + responses: + "201": + content: + application/json: + schema: + $ref: '#/components/schemas/TypesExecution' + description: Created + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - pipeline + /pipelines/{pipeline_ref}/executions/{execution_number}: + delete: + operationId: deleteExecution + parameters: + - in: path + name: pipeline_ref + required: true + schema: + type: string + - in: path + name: execution_number + required: true + schema: + type: string + responses: + "204": + description: No Content + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - pipeline + get: + operationId: findExecution + parameters: + - in: path + name: pipeline_ref + required: true + schema: + type: string + - in: path + name: execution_number + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/TypesExecution' + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - pipeline + patch: + operationId: updateExecution + parameters: + - in: path + name: pipeline_ref + required: true + schema: + type: string + - in: path + name: execution_number + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpenapiUpdateExecutionRequest' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/TypesExecution' + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - pipeline /principals: get: operationId: listPrincipals parameters: - - description: The substring by which the principals are filtered. - in: query - name: query - required: false - schema: + - description: The substring by which the principals are filtered. + in: query + name: query + required: false + schema: + type: string + - description: The account ID the principals are retrieved for (Not required + in standalone). + in: query + name: accountIdentifier + required: false + schema: + type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - description: The types of principals to include. + in: query + name: type + required: false + schema: + items: + enum: + - service + - serviceaccount + - user type: string - - description: The account ID the principals are retrieved for (Not required - in standalone). - in: query - name: accountIdentifier - required: false - schema: - type: string - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - description: The types of principals to include. - in: query - name: type - required: false - schema: - items: - enum: - - service - - serviceaccount - - user - type: string - type: array + type: array responses: - '200': + "200": content: application/json: schema: @@ -352,26 +777,26 @@ paths: $ref: '#/components/schemas/TypesPrincipalInfo' type: array description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - principals + - principals /register: post: operationId: onRegister @@ -381,245 +806,245 @@ paths: schema: $ref: '#/components/schemas/OpenapiRegisterRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesTokenResponse' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - account + - account /repos: post: operationId: createRepository parameters: - - description: path of parent space (Not needed in standalone). - in: query - name: space_path - required: false - schema: - default: false - type: string + - description: path of parent space (Not needed in standalone). + in: query + name: space_path + required: false + schema: + default: false + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCreateRepositoryRequest' responses: - '201': + "201": content: application/json: schema: $ref: '#/components/schemas/TypesRepository' description: Created - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}: delete: operationId: deleteRepository parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string responses: - '204': + "204": description: No Content - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository get: operationId: findRepository parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesRepository' description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository patch: operationId: updateRepository parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiUpdateRepoRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesRepository' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/blame/{path}: get: operationId: getBlame parameters: - - description: The git reference (branch / tag / commitID) that will be used - to retrieve the data. If no value is provided the default branch of the - repository is used. - in: query - name: git_ref - required: false - schema: - default: '{Repository Default Branch}' - type: string - - description: Line number from which the file data is considered - in: query - name: line_from - required: false - schema: - default: 0 - type: integer - - description: Line number to which the file data is considered - in: query - name: line_to - required: false - schema: - default: 0 - type: integer - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: path - required: true - schema: - type: string + - description: The git reference (branch / tag / commitID) that will be used + to retrieve the data. If no value is provided the default branch of the + repository is used. + in: query + name: git_ref + required: false + schema: + default: '{Repository Default Branch}' + type: string + - description: Line number from which the file data is considered + in: query + name: line_from + required: false + schema: + default: 0 + type: integer + - description: Line number to which the file data is considered + in: query + name: line_to + required: false + schema: + default: 0 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: path + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -627,94 +1052,94 @@ paths: $ref: '#/components/schemas/GitrpcBlamePart' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/branches: get: operationId: listBranches parameters: - - description: Indicates whether optional commit information should be included - in the response. - in: query - name: include_commit - required: false - schema: - default: false - type: boolean - - description: The substring by which the branches are filtered. - in: query - name: query - required: false - schema: - type: string - - description: The order of the output. - in: query - name: order - required: false - schema: - default: asc - enum: - - asc - - desc - type: string - - description: The data by which the branches are sorted. - in: query - name: sort - required: false - schema: - default: name - enum: - - name - - date - type: string - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: repo_ref - required: true - schema: - type: string + - description: Indicates whether optional commit information should be included + in the response. + in: query + name: include_commit + required: false + schema: + default: false + type: boolean + - description: The substring by which the branches are filtered. + in: query + name: query + required: false + schema: + type: string + - description: The order of the output. + in: query + name: order + required: false + schema: + default: asc + enum: + - asc + - desc + type: string + - description: The data by which the branches are sorted. + in: query + name: sort + required: false + schema: + default: name + enum: + - name + - date + type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -722,183 +1147,200 @@ paths: $ref: '#/components/schemas/RepoBranch' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository post: operationId: createBranch parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCreateBranchRequest' responses: - '201': + "201": content: application/json: schema: $ref: '#/components/schemas/RepoBranch' description: Created - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/branches/{branch_name}: delete: operationId: deleteBranch parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: branch_name - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: branch_name + required: true + schema: + type: string responses: - '204': + "204": description: No Content - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository get: operationId: getBranch parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: branch_name - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: branch_name + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/RepoBranch' description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/checks/commits/{commit_sha}: get: operationId: listStatusCheckResults parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: commit_sha - required: true - schema: - type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: commit_sha + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -906,152 +1348,163 @@ paths: $ref: '#/components/schemas/TypesCheck' type: array description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - status_checks + - status_checks put: operationId: reportStatusCheckResults parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: commit_sha - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: commit_sha + required: true + schema: + type: string requestBody: content: application/json: schema: - $ref: '#/components/schemas/OpenapiReportStatusCheckResultRequest' + properties: + check_uid: + type: string + link: + type: string + payload: + $ref: '#/components/schemas/TypesCheckPayload' + status: + $ref: '#/components/schemas/EnumCheckStatus' + summary: + type: string + type: object responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesCheck' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - status_checks + - status_checks /repos/{repo_ref}/commits: get: operationId: listCommits parameters: - - description: The git reference (branch / tag / commitID) that will be used - to retrieve the data. If no value is provided the default branch of the - repository is used. - in: query - name: git_ref - required: false - schema: - default: '{Repository Default Branch}' - type: string - - description: The result should only contain commits that occurred after the - provided reference. - in: query - name: after - required: false - schema: - type: string - - description: Path for which commit information should be retrieved - in: query - name: path - required: false - schema: - default: '' - type: string - - description: Epoch since when commit information should be retrieved. - in: query - name: since - required: false - schema: - type: integer - - description: Epoch until when commit information should be retrieved. - in: query - name: until - required: false - schema: - type: integer - - description: Committer pattern for which commit information should be retrieved. - in: query - name: committer - required: false - schema: - type: string - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: repo_ref - required: true - schema: - type: string + - description: The git reference (branch / tag / commitID) that will be used + to retrieve the data. If no value is provided the default branch of the + repository is used. + in: query + name: git_ref + required: false + schema: + default: '{Repository Default Branch}' + type: string + - description: The result should only contain commits that occurred after the + provided reference. + in: query + name: after + required: false + schema: + type: string + - description: Path for which commit information should be retrieved + in: query + name: path + required: false + schema: + default: "" + type: string + - description: Epoch since when commit information should be retrieved. + in: query + name: since + required: false + schema: + type: integer + - description: Epoch until when commit information should be retrieved. + in: query + name: until + required: false + schema: + type: integer + - description: Committer pattern for which commit information should be retrieved. + in: query + name: committer + required: false + schema: + type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -1059,147 +1512,147 @@ paths: $ref: '#/components/schemas/TypesListCommitResponse' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository post: operationId: commitFiles parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCommitFilesRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/RepoCommitFilesResponse' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/commits/{commit_sha}: get: operationId: getCommit parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: commit_sha - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: commit_sha + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesCommit' description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/commits/calculate-divergence: post: operationId: calculateCommitDivergence parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCalculateCommitDivergenceRequest' responses: - '200': + "200": content: application/json: schema: @@ -1207,297 +1660,358 @@ paths: $ref: '#/components/schemas/RepoCommitDivergence' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository - /repos/{repo_ref}/compare/{range}: - get: - operationId: rawDiff - parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: range - required: true - schema: - example: main..dev - type: string - responses: - '200': - content: - text/plain: - schema: - type: string - description: OK - '401': - content: - application/json: - schema: - $ref: '#/components/schemas/UsererrorError' - description: Unauthorized - '403': - content: - application/json: - schema: - $ref: '#/components/schemas/UsererrorError' - description: Forbidden - '500': - content: - application/json: - schema: - $ref: '#/components/schemas/UsererrorError' - description: Internal Server Error - tags: - - repository + - repository /repos/{repo_ref}/content/{path}: get: operationId: getContent parameters: - - description: The git reference (branch / tag / commitID) that will be used - to retrieve the data. If no value is provided the default branch of the - repository is used. - in: query - name: git_ref - required: false - schema: - default: '{Repository Default Branch}' - type: string - - description: Indicates whether optional commit information should be included - in the response. - in: query - name: include_commit - required: false - schema: - default: false - type: boolean - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: path - required: true - schema: - type: string + - description: The git reference (branch / tag / commitID) that will be used + to retrieve the data. If no value is provided the default branch of the + repository is used. + in: query + name: git_ref + required: false + schema: + default: '{Repository Default Branch}' + type: string + - description: Indicates whether optional commit information should be included + in the response. + in: query + name: include_commit + required: false + schema: + default: false + type: boolean + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: path + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/OpenapiGetContentOutput' description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/diff-stats/{range}: get: operationId: diffStats parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: range - required: true - schema: - example: main..dev - type: string + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: range + required: true + schema: + example: main..dev + type: string responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesDiffStats' description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository + /repos/{repo_ref}/diff/{range}: + get: + operationId: rawDiff + parameters: + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: range + required: true + schema: + example: main..dev + type: string + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/GitrpcFileDiff' + type: array + text/plain: + schema: + type: string + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - repository /repos/{repo_ref}/merge-check/{range}: post: operationId: mergeCheck parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: range - required: true - schema: - example: main..dev - type: string + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: range + required: true + schema: + example: main..dev + type: string responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/RepoMergeCheck' description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/move: post: operationId: moveRepository parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiMoveRepoRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesRepository' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository + /repos/{repo_ref}/path-details: + post: + operationId: pathDetails + parameters: + - description: The git reference (branch / tag / commitID) that will be used + to retrieve the data. If no value is provided the default branch of the + repository is used. + in: query + name: git_ref + required: false + schema: + default: '{Repository Default Branch}' + type: string + - in: path + name: repo_ref + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpenapiPathsDetailsRequest' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/RepoPathsDetailsOutput' + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - repository /repos/{repo_ref}/paths: get: operationId: listRepositoryPaths parameters: - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: repo_ref - required: true - schema: - type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -1505,214 +2019,214 @@ paths: $ref: '#/components/schemas/TypesPath' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository post: operationId: createRepositoryPath parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCreateRepoPathRequest' responses: - '201': + "201": content: application/json: schema: $ref: '#/components/schemas/TypesPath' description: Created - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/paths/{path_id}: delete: operationId: deleteRepositoryPath parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: path_id - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: path_id + required: true + schema: + type: string responses: - '204': + "204": description: No Content - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/pullreq: get: operationId: listPullReq parameters: - - description: The state of the pull requests to include in the result. - in: query - name: state - required: false - schema: - items: - default: open - enum: - - closed - - merged - - open - type: string - type: array - - description: Source repository ref of the pull requests. - in: query - name: source_repo_ref - required: false - schema: - type: string - - description: Source branch of the pull requests. - in: query - name: source_branch - required: false - schema: - type: string - - description: Target branch of the pull requests. - in: query - name: target_branch - required: false - schema: - type: string - - description: The substring by which the pull requests are filtered. - in: query - name: query - required: false - schema: - type: string - - description: The principal ID who created pull requests. - in: query - name: created_by - required: false - schema: - type: integer - - description: The order of the output. - in: query - name: order - required: false - schema: - default: asc + - description: The state of the pull requests to include in the result. + in: query + name: state + required: false + schema: + items: + default: open enum: - - asc - - desc - type: string - - description: The data by which the pull requests are sorted. - in: query - name: sort - required: false - schema: - default: number - enum: - - created - - edited - - merged - - number - type: string - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: repo_ref - required: true - schema: + - closed + - merged + - open type: string + type: array + - description: Source repository ref of the pull requests. + in: query + name: source_repo_ref + required: false + schema: + type: string + - description: Source branch of the pull requests. + in: query + name: source_branch + required: false + schema: + type: string + - description: Target branch of the pull requests. + in: query + name: target_branch + required: false + schema: + type: string + - description: The substring by which the pull requests are filtered. + in: query + name: query + required: false + schema: + type: string + - description: The principal ID who created pull requests. + in: query + name: created_by + required: false + schema: + type: integer + - description: The order of the output. + in: query + name: order + required: false + schema: + default: asc + enum: + - asc + - desc + type: string + - description: The data by which the pull requests are sorted. + in: query + name: sort + required: false + schema: + default: number + enum: + - created + - edited + - merged + - number + type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -1720,246 +2234,246 @@ paths: $ref: '#/components/schemas/TypesPullReq' type: array description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq post: operationId: createPullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCreatePullReqRequest' responses: - '201': + "201": content: application/json: schema: $ref: '#/components/schemas/TypesPullReq' description: Created - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}: get: operationId: getPullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesPullReq' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq patch: operationId: updatePullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiUpdatePullReqRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesPullReq' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}/activities: get: operationId: listPullReqActivities parameters: - - description: The kind of the pull request activity to include in the result. - in: query - name: kind - required: false - schema: - items: - enum: - - change-comment - - comment - - system - type: string - type: array - - description: The type of the pull request activity to include in the result. - in: query - name: type - required: false - schema: - items: - enum: - - branch-delete - - branch-update - - code-comment - - comment - - merge - - review-submit - - state-change - - title-change - type: string - type: array - - description: The result should contain only entries created at and after this - timestamp (unix millis). - in: query - name: after - required: false - schema: - minimum: 0 - type: integer - - description: The result should contain only entries created before this timestamp - (unix millis). - in: query - name: before - required: false - schema: - minimum: 0 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: repo_ref - required: true - schema: + - description: The kind of the pull request activity to include in the result. + in: query + name: kind + required: false + schema: + items: + enum: + - change-comment + - comment + - system type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer + type: array + - description: The type of the pull request activity to include in the result. + in: query + name: type + required: false + schema: + items: + enum: + - branch-delete + - branch-update + - code-comment + - comment + - merge + - review-submit + - state-change + - title-change + type: string + type: array + - description: The result should contain only entries created at and after this + timestamp (unix millis). + in: query + name: after + required: false + schema: + minimum: 0 + type: integer + - description: The result should contain only entries created before this timestamp + (unix millis). + in: query + name: before + required: false + schema: + minimum: 0 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer responses: - '200': + "200": content: application/json: schema: @@ -1967,278 +2481,278 @@ paths: $ref: '#/components/schemas/TypesPullReqActivity' type: array description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}/comments: post: operationId: commentCreatePullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCommentCreatePullReqRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesPullReqActivity' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}/comments/{pullreq_comment_id}: delete: operationId: commentDeletePullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer - - in: path - name: pullreq_comment_id - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer + - in: path + name: pullreq_comment_id + required: true + schema: + type: integer responses: - '204': + "204": description: No Content - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq patch: operationId: commentUpdatePullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer - - in: path - name: pullreq_comment_id - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer + - in: path + name: pullreq_comment_id + required: true + schema: + type: integer requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCommentUpdatePullReqRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesPullReqActivity' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}/comments/{pullreq_comment_id}/status: put: operationId: commentStatusPullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer - - in: path - name: pullreq_comment_id - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer + - in: path + name: pullreq_comment_id + required: true + schema: + type: integer requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCommentStatusPullReqRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesPullReqActivity' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}/commits: get: operationId: listPullReqCommits parameters: - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer responses: - '200': + "200": content: application/json: schema: @@ -2246,208 +2760,165 @@ paths: $ref: '#/components/schemas/TypesCommit' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq - /repos/{repo_ref}/pullreq/{pullreq_number}/diff: - get: - operationId: rawPullReqDiff - parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer - responses: - '200': - content: - text/plain: - schema: - type: string - description: OK - '401': - content: - application/json: - schema: - $ref: '#/components/schemas/UsererrorError' - description: Unauthorized - '403': - content: - application/json: - schema: - $ref: '#/components/schemas/UsererrorError' - description: Forbidden - '404': - content: - application/json: - schema: - $ref: '#/components/schemas/UsererrorError' - description: Not Found - '500': - content: - application/json: - schema: - $ref: '#/components/schemas/UsererrorError' - description: Internal Server Error - tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}/merge: post: operationId: mergePullReqOp parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiMergePullReq' responses: - '200': + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/TypesMergeResponse' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '405': + "405": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Method Not Allowed - '409': + "409": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Conflict - '422': + "422": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unprocessable Entity tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}/metadata: get: operationId: pullReqMetaData parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesPullReqStats' description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}/reviewers: get: operationId: reviewerListPullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer responses: - '200': + "200": content: application/json: schema: @@ -2455,294 +2926,294 @@ paths: $ref: '#/components/schemas/TypesPullReqReviewer' type: array description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq put: operationId: reviewerAddPullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiReviewerAddPullReqRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesPullReqReviewer' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}/reviewers/{pullreq_reviewer_id}: delete: operationId: reviewerDeletePullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer - - in: path - name: pullreq_reviewer_id - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer + - in: path + name: pullreq_reviewer_id + required: true + schema: + type: integer responses: - '204': + "204": description: No Content - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}/reviews: post: operationId: reviewSubmitPullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiReviewSubmitPullReqRequest' responses: - '204': + "204": description: No Content - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq /repos/{repo_ref}/pullreq/{pullreq_number}/state: post: operationId: statePullReq parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: pullreq_number - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: pullreq_number + required: true + schema: + type: integer requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiStatePullReqRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesPullReq' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - pullreq + - pullreq /repos/{repo_ref}/raw/{path}: get: operationId: getRaw parameters: - - description: The git reference (branch / tag / commitID) that will be used - to retrieve the data. If no value is provided the default branch of the - repository is used. - in: query - name: git_ref - required: false - schema: - default: '{Repository Default Branch}' - type: string - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: path - required: true - schema: - type: string + - description: The git reference (branch / tag / commitID) that will be used + to retrieve the data. If no value is provided the default branch of the + repository is used. + in: query + name: git_ref + required: false + schema: + default: '{Repository Default Branch}' + type: string + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: path + required: true + schema: + type: string responses: - '200': + "200": description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/service-accounts: get: operationId: listRepositoryServiceAccounts parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -2750,94 +3221,94 @@ paths: $ref: '#/components/schemas/TypesServiceAccount' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/tags: get: operationId: listTags parameters: - - description: Indicates whether optional commit information should be included - in the response. - in: query - name: include_commit - required: false - schema: - default: false - type: boolean - - description: The substring by which the tags are filtered. - in: query - name: query - required: false - schema: - type: string - - description: The order of the output. - in: query - name: order - required: false - schema: - default: asc - enum: - - asc - - desc - type: string - - description: The data by which the tags are sorted. - in: query - name: sort - required: false - schema: - default: name - enum: - - name - - date - type: string - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: repo_ref - required: true - schema: - type: string + - description: Indicates whether optional commit information should be included + in the response. + in: query + name: include_commit + required: false + schema: + default: false + type: boolean + - description: The substring by which the tags are filtered. + in: query + name: query + required: false + schema: + type: string + - description: The order of the output. + in: query + name: order + required: false + schema: + default: asc + enum: + - asc + - desc + type: string + - description: The data by which the tags are sorted. + in: query + name: sort + required: false + schema: + default: name + enum: + - name + - date + type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -2845,190 +3316,190 @@ paths: $ref: '#/components/schemas/RepoCommitTag' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository post: operationId: createTag parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCreateTagRequest' responses: - '201': + "201": content: application/json: schema: $ref: '#/components/schemas/RepoCommitTag' description: Created - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '409': + "409": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Conflict - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/tags/{tag_name}: delete: operationId: deleteTag parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: tag_name - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: tag_name + required: true + schema: + type: string responses: - '204': + "204": description: No Content - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '409': + "409": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Conflict - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - repository + - repository /repos/{repo_ref}/webhooks: get: operationId: listWebhooks parameters: - - description: The substring which is used to filter the spaces by their path - name. - in: query - name: query - required: false - schema: - type: string - - description: The data by which the webhooks are sorted. - in: query - name: sort - required: false - schema: - default: id - enum: - - id - - display_name - - created - - updated - type: string - - description: The order of the output. - in: query - name: order - required: false - schema: - default: asc - enum: - - asc - - desc - type: string - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: repo_ref - required: true - schema: - type: string + - description: The substring which is used to filter the spaces by their path + name. + in: query + name: query + required: false + schema: + type: string + - description: The data by which the webhooks are sorted. + in: query + name: sort + required: false + schema: + default: id + enum: + - id + - display_name + - created + - updated + type: string + - description: The order of the output. + in: query + name: order + required: false + schema: + default: asc + enum: + - asc + - desc + type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -3036,251 +3507,251 @@ paths: $ref: '#/components/schemas/OpenapiWebhookType' type: array description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - webhook + - webhook post: operationId: createWebhook parameters: - - in: path - name: repo_ref - required: true - schema: - type: string + - in: path + name: repo_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCreateWebhookRequest' responses: - '201': + "201": content: application/json: schema: $ref: '#/components/schemas/OpenapiWebhookType' description: Created - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - webhook + - webhook /repos/{repo_ref}/webhooks/{webhook_id}: delete: operationId: deleteWebhook parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: webhook_id - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: webhook_id + required: true + schema: + type: integer responses: - '204': + "204": description: No Content - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - webhook + - webhook get: operationId: getWebhook parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: webhook_id - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: webhook_id + required: true + schema: + type: integer responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/OpenapiWebhookType' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - webhook + - webhook patch: operationId: updateWebhook parameters: - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: webhook_id - required: true - schema: - type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: webhook_id + required: true + schema: + type: integer requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiUpdateWebhookRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/OpenapiWebhookType' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - webhook + - webhook /repos/{repo_ref}/webhooks/{webhook_id}/executions: get: operationId: listWebhookExecutions parameters: - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: webhook_id - required: true - schema: - type: integer + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: webhook_id + required: true + schema: + type: integer responses: - '200': + "200": content: application/json: schema: @@ -3288,106 +3759,106 @@ paths: $ref: '#/components/schemas/TypesWebhookExecution' type: array description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - webhook + - webhook /repos/{repo_ref}/webhooks/{webhook_id}/executions/{webhook_execution_id}: get: operationId: getWebhookExecution parameters: - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: repo_ref - required: true - schema: - type: string - - in: path - name: webhook_id - required: true - schema: - type: integer - - in: path - name: webhook_execution_id - required: true - schema: - type: integer + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: repo_ref + required: true + schema: + type: string + - in: path + name: webhook_id + required: true + schema: + type: integer + - in: path + name: webhook_execution_id + required: true + schema: + type: integer responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesWebhookExecution' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - webhook + - webhook /resources/gitignore: get: operationId: listGitignore responses: - '200': + "200": content: application/json: schema: @@ -3395,31 +3866,31 @@ paths: type: string type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - resource + - resource /resources/license: get: operationId: listLicenses responses: - '200': + "200": content: application/json: schema: @@ -3432,26 +3903,198 @@ paths: type: object type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - resource + - resource + /secrets: + post: + operationId: createSecret + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpenapiCreateSecretRequest' + responses: + "201": + content: + application/json: + schema: + $ref: '#/components/schemas/TypesSecret' + description: Created + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - secret + /secrets/{secret_ref}: + delete: + operationId: deleteSecret + parameters: + - in: path + name: secret_ref + required: true + schema: + type: string + responses: + "204": + description: No Content + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - secret + get: + operationId: findSecret + parameters: + - in: path + name: secret_ref + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/TypesSecret' + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - secret + patch: + operationId: updateSecret + parameters: + - in: path + name: secret_ref + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OpenapiUpdateSecretRequest' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/TypesSecret' + description: OK + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - secret /spaces: post: operationId: createSpace @@ -3461,221 +4104,264 @@ paths: schema: $ref: '#/components/schemas/OpenapiCreateSpaceRequest' responses: - '201': + "201": content: application/json: schema: $ref: '#/components/schemas/TypesSpace' description: Created - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space /spaces/{space_ref}: delete: operationId: deleteSpace parameters: - - in: path - name: space_ref - required: true - schema: - type: string + - in: path + name: space_ref + required: true + schema: + type: string responses: - '204': + "204": description: No Content - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space get: operationId: getSpace parameters: - - in: path - name: space_ref - required: true - schema: - type: string + - in: path + name: space_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesSpace' description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space patch: operationId: updateSpace parameters: - - in: path - name: space_ref - required: true - schema: - type: string + - in: path + name: space_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiUpdateSpaceRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesSpace' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space /spaces/{space_ref}/members: get: operationId: membershipList parameters: - - in: path - name: space_ref - required: true - schema: - type: string + - description: The substring by which the space members are filtered. + in: query + name: query + required: false + schema: + type: string + - description: The order of the output. + in: query + name: order + required: false + schema: + default: asc + enum: + - asc + - desc + type: string + - description: The field by which the space members are sorted. + in: query + name: sort + required: false + schema: + default: name + enum: + - created + - name + type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: space_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: items: - $ref: '#/components/schemas/TypesMembership' + $ref: '#/components/schemas/TypesMembershipUser' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space post: operationId: membershipAdd parameters: - - in: path - name: space_ref - required: true - schema: - type: string + - in: path + name: space_ref + required: true + schema: + type: string requestBody: content: application/json: @@ -3687,94 +4373,94 @@ paths: type: string type: object responses: - '201': + "201": content: application/json: schema: - $ref: '#/components/schemas/TypesMembership' + $ref: '#/components/schemas/TypesMembershipUser' description: Created - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space /spaces/{space_ref}/members/{user_uid}: delete: operationId: membershipDelete parameters: - - in: path - name: space_ref - required: true - schema: - type: string - - in: path - name: user_uid - required: true - schema: - type: string + - in: path + name: space_ref + required: true + schema: + type: string + - in: path + name: user_uid + required: true + schema: + type: string responses: - '204': + "204": description: No Content - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space patch: operationId: membershipUpdate parameters: - - in: path - name: space_ref - required: true - schema: - type: string - - in: path - name: user_uid - required: true - schema: - type: string + - in: path + name: space_ref + required: true + schema: + type: string + - in: path + name: user_uid + required: true + schema: + type: string requestBody: content: application/json: @@ -3784,113 +4470,113 @@ paths: $ref: '#/components/schemas/EnumMembershipRole' type: object responses: - '200': + "200": content: application/json: schema: - $ref: '#/components/schemas/TypesMembership' + $ref: '#/components/schemas/TypesMembershipUser' description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space /spaces/{space_ref}/move: post: operationId: moveSpace parameters: - - in: path - name: space_ref - required: true - schema: - type: string + - in: path + name: space_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiMoveSpaceRequest' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesSpace' description: OK - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space /spaces/{space_ref}/paths: get: operationId: listPaths parameters: - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: space_ref - required: true - schema: - type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: space_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -3898,178 +4584,246 @@ paths: $ref: '#/components/schemas/TypesPath' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space post: operationId: createPath parameters: - - in: path - name: space_ref - required: true - schema: - type: string + - in: path + name: space_ref + required: true + schema: + type: string requestBody: content: application/json: schema: $ref: '#/components/schemas/OpenapiCreatePathRequest' responses: - '201': + "201": content: application/json: schema: $ref: '#/components/schemas/TypesPath' description: Created - '400': + "400": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Bad Request - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space /spaces/{space_ref}/paths/{path_id}: delete: operationId: deletePath parameters: - - in: path - name: space_ref - required: true - schema: - type: string - - in: path - name: path_id - required: true - schema: - type: string + - in: path + name: space_ref + required: true + schema: + type: string + - in: path + name: path_id + required: true + schema: + type: string responses: - '204': + "204": description: No Content - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space + /spaces/{space_ref}/pipelines: + get: + operationId: listPipelines + parameters: + - description: The substring which is used to filter the repositories by their + path name. + in: query + name: query + required: false + schema: + type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: space_ref + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/TypesPipeline' + type: array + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - space /spaces/{space_ref}/repos: get: operationId: listRepos parameters: - - description: The substring which is used to filter the repositories by their - path name. - in: query - name: query - required: false - schema: - type: string - - description: The data by which the repositories are sorted. - in: query - name: sort - required: false - schema: - default: uid - enum: - - uid - - path - - created - - updated - type: string - - description: The order of the output. - in: query - name: order - required: false - schema: - default: asc - enum: - - asc - - desc - type: string - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: space_ref - required: true - schema: - type: string + - description: The substring which is used to filter the repositories by their + path name. + in: query + name: query + required: false + schema: + type: string + - description: The data by which the repositories are sorted. + in: query + name: sort + required: false + schema: + default: uid + enum: + - uid + - path + - created + - updated + type: string + - description: The order of the output. + in: query + name: order + required: false + schema: + default: asc + enum: + - asc + - desc + type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: space_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -4077,43 +4831,111 @@ paths: $ref: '#/components/schemas/TypesRepository' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space + /spaces/{space_ref}/secrets: + get: + operationId: listSecrets + parameters: + - description: The substring which is used to filter the repositories by their + path name. + in: query + name: query + required: false + schema: + type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: space_ref + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/TypesSecret' + type: array + description: OK + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Not Found + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - space /spaces/{space_ref}/service-accounts: get: operationId: listServiceAccounts parameters: - - in: path - name: space_ref - required: true - schema: - type: string + - in: path + name: space_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -4121,89 +4943,89 @@ paths: $ref: '#/components/schemas/TypesServiceAccount' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space /spaces/{space_ref}/spaces: get: operationId: listSpaces parameters: - - description: The substring which is used to filter the spaces by their path - name. - in: query - name: query - required: false - schema: - type: string - - description: The data by which the spaces are sorted. - in: query - name: sort - required: false - schema: - default: uid - enum: - - uid - - path - - created - - updated - type: string - - description: The order of the output. - in: query - name: order - required: false - schema: - default: asc - enum: - - asc - - desc - type: string - - description: The page to return. - in: query - name: page - required: false - schema: - default: 1 - minimum: 1 - type: integer - - description: The maximum number of results to return. - in: query - name: limit - required: false - schema: - default: 30 - maximum: 100 - minimum: 1 - type: integer - - in: path - name: space_ref - required: true - schema: - type: string + - description: The substring which is used to filter the spaces by their path + name. + in: query + name: query + required: false + schema: + type: string + - description: The data by which the spaces are sorted. + in: query + name: sort + required: false + schema: + default: uid + enum: + - uid + - path + - created + - updated + type: string + - description: The order of the output. + in: query + name: order + required: false + schema: + default: asc + enum: + - asc + - desc + type: string + - description: The page to return. + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: The maximum number of results to return. + in: query + name: limit + required: false + schema: + default: 30 + maximum: 100 + minimum: 1 + type: integer + - in: path + name: space_ref + required: true + schema: + type: string responses: - '200': + "200": content: application/json: schema: @@ -4211,50 +5033,50 @@ paths: $ref: '#/components/schemas/TypesSpace' type: array description: OK - '401': + "401": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Unauthorized - '403': + "403": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Forbidden - '404': + "404": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Not Found - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - space + - space /user: get: operationId: getUser responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesUser' description: OK - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - user + - user patch: operationId: updateUser requestBody: @@ -4263,20 +5085,40 @@ paths: schema: $ref: '#/components/schemas/UserUpdateInput' responses: - '200': + "200": content: application/json: schema: $ref: '#/components/schemas/TypesUser' description: OK - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - user + - user + /user/memberships: + get: + operationId: membershipSpaces + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/TypesMembershipSpace' + type: array + description: OK + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/UsererrorError' + description: Internal Server Error + tags: + - user /user/token: post: operationId: createToken @@ -4286,135 +5128,144 @@ paths: schema: $ref: '#/components/schemas/OpenapiCreateTokenRequest' responses: - '201': + "201": content: application/json: schema: $ref: '#/components/schemas/TypesTokenResponse' description: Created - '500': + "500": content: application/json: schema: $ref: '#/components/schemas/UsererrorError' description: Internal Server Error tags: - - user + - user components: schemas: EnumAccessGrant: type: integer EnumCheckPayloadKind: enum: - - external + - "" + - markdown + - raw type: string EnumCheckStatus: enum: - - error - - failure - - pending - - running - - success + - error + - failure + - pending + - running + - success type: string EnumContentEncodingType: enum: - - base64 - - utf8 + - base64 + - utf8 type: string EnumMembershipRole: enum: - - contributor - - executor - - reader - - space_owner + - contributor + - executor + - reader + - space_owner type: string EnumMergeCheckStatus: type: string EnumMergeMethod: enum: - - merge - - squash - - rebase + - merge + - squash + - rebase type: string EnumParentResourceType: enum: - - space - - repo + - space + - repo type: string EnumPathTargetType: type: string EnumPrincipalType: enum: - - service - - serviceaccount - - user + - service + - serviceaccount + - user type: string EnumPullReqActivityKind: enum: - - change-comment - - comment - - system + - change-comment + - comment + - system type: string EnumPullReqActivityType: enum: - - branch-delete - - branch-update - - code-comment - - comment - - merge - - review-submit - - state-change - - title-change + - branch-delete + - branch-update + - code-comment + - comment + - merge + - review-submit + - state-change + - title-change type: string EnumPullReqCommentStatus: enum: - - active - - resolved + - active + - resolved type: string EnumPullReqReviewDecision: enum: - - approved - - changereq - - pending - - reviewed + - approved + - changereq + - pending + - reviewed type: string EnumPullReqReviewerType: enum: - - assigned - - requested - - self_assigned + - assigned + - requested + - self_assigned type: string EnumPullReqState: enum: - - closed - - merged - - open + - closed + - merged + - open + type: string + EnumScmType: + enum: + - GITNESS + - GITHUB + - GITLAB + - UNKNOWN type: string EnumTokenType: type: string EnumWebhookExecutionResult: enum: - - fatal_error - - retriable_error - - success + - fatal_error + - retriable_error + - success nullable: true type: string EnumWebhookParent: enum: - - repo - - space + - repo + - space type: string EnumWebhookTrigger: enum: - - branch_created - - branch_deleted - - branch_updated - - pullreq_branch_updated - - pullreq_created - - pullreq_reopened - - tag_created - - tag_deleted - - tag_updated + - branch_created + - branch_deleted + - branch_updated + - pullreq_branch_updated + - pullreq_created + - pullreq_reopened + - tag_created + - tag_deleted + - tag_updated type: string GitrpcBlamePart: properties: @@ -4427,7 +5278,6 @@ components: type: array type: object GitrpcCommit: - nullable: true properties: author: $ref: '#/components/schemas/GitrpcSignature' @@ -4442,11 +5292,41 @@ components: type: object GitrpcFileAction: enum: - - CREATE - - UPDATE - - DELETE - - MOVE + - CREATE + - UPDATE + - DELETE + - MOVE type: string + GitrpcFileDiff: + properties: + additions: + type: integer + changes: + type: integer + content_url: + type: string + deletions: + type: integer + is_binary: + type: boolean + is_submodule: + type: boolean + old_path: + type: string + old_sha: + type: string + patch: + items: + minimum: 0 + type: integer + type: array + path: + type: string + sha: + type: string + status: + type: string + type: object GitrpcIdentity: properties: email: @@ -4454,6 +5334,15 @@ components: name: type: string type: object + GitrpcPathDetails: + properties: + last_commit: + $ref: '#/components/schemas/GitrpcCommit' + path: + type: string + size: + type: integer + type: object GitrpcSignature: properties: identity: @@ -4544,10 +5433,10 @@ components: type: object OpenapiContent: oneOf: - - $ref: '#/components/schemas/RepoFileContent' - - $ref: '#/components/schemas/OpenapiDirContent' - - $ref: '#/components/schemas/RepoSymlinkContent' - - $ref: '#/components/schemas/RepoSubmoduleContent' + - $ref: '#/components/schemas/RepoFileContent' + - $ref: '#/components/schemas/OpenapiDirContent' + - $ref: '#/components/schemas/RepoSymlinkContent' + - $ref: '#/components/schemas/RepoSubmoduleContent' type: object OpenapiContentInfo: properties: @@ -4564,17 +5453,21 @@ components: type: object OpenapiContentType: enum: - - file - - dir - - symlink - - submodule + - file + - dir + - symlink + - submodule type: string OpenapiCreateBranchRequest: properties: name: type: string target: - nullable: true + type: string + type: object + OpenapiCreateExecutionRequest: + properties: + status: type: string type: object OpenapiCreatePathRequest: @@ -4582,6 +5475,23 @@ components: path: type: string type: object + OpenapiCreatePipelineRequest: + properties: + config_path: + type: string + default_branch: + type: string + description: + type: string + repo_ref: + type: string + repo_type: + $ref: '#/components/schemas/EnumScmType' + space_ref: + type: string + uid: + type: string + type: object OpenapiCreatePullReqRequest: properties: description: @@ -4623,6 +5533,17 @@ components: uid: type: string type: object + OpenapiCreateSecretRequest: + properties: + data: + type: string + description: + type: string + space_ref: + type: string + uid: + type: string + type: object OpenapiCreateSpaceRequest: properties: description: @@ -4637,12 +5558,10 @@ components: OpenapiCreateTagRequest: properties: message: - nullable: true type: string name: type: string target: - nullable: true type: string type: object OpenapiCreateTokenRequest: @@ -4733,6 +5652,14 @@ components: nullable: true type: string type: object + OpenapiPathsDetailsRequest: + properties: + paths: + items: + type: string + nullable: true + type: array + type: object OpenapiRegisterRequest: properties: display_name: @@ -4744,19 +5671,6 @@ components: uid: type: string type: object - OpenapiReportStatusCheckResultRequest: - properties: - check_uid: - type: string - link: - type: string - payload: - $ref: '#/components/schemas/TypesCheckPayload' - status: - $ref: '#/components/schemas/EnumCheckStatus' - summary: - type: string - type: object OpenapiReviewSubmitPullReqRequest: properties: commit_sha: @@ -4785,6 +5699,20 @@ components: admin: type: boolean type: object + OpenapiUpdateExecutionRequest: + properties: + status: + type: string + type: object + OpenapiUpdatePipelineRequest: + properties: + config_path: + type: string + description: + type: string + uid: + type: string + type: object OpenapiUpdatePullReqRequest: properties: description: @@ -4801,6 +5729,15 @@ components: nullable: true type: boolean type: object + OpenapiUpdateSecretRequest: + properties: + data: + type: string + description: + type: string + uid: + type: string + type: object OpenapiUpdateSpaceRequest: properties: description: @@ -4966,6 +5903,14 @@ components: mergeable: type: boolean type: object + RepoPathsDetailsOutput: + properties: + details: + items: + $ref: '#/components/schemas/GitrpcPathDetails' + nullable: true + type: array + type: object RepoSubmoduleContent: properties: commit_sha: @@ -4981,6 +5926,7 @@ components: type: string type: object TimeDuration: + nullable: true type: integer TypesCheck: properties: @@ -5051,6 +5997,79 @@ components: files_changed: type: integer type: object + TypesExecution: + properties: + action: + type: string + after: + type: string + author_avatar: + type: string + author_email: + type: string + author_login: + type: string + author_name: + type: string + before: + type: string + created: + type: integer + cron: + type: string + debug: + type: boolean + deploy_id: + type: integer + deploy_to: + type: string + error: + type: string + event: + type: string + finished: + type: integer + id: + type: integer + link: + type: string + message: + type: string + number: + type: integer + params: + type: string + parent: + type: integer + pipeline_id: + type: integer + ref: + type: string + repo_id: + type: integer + sender: + type: string + source: + type: string + source_repo: + type: string + started: + type: integer + status: + type: string + target: + type: string + timestamp: + type: integer + title: + type: string + trigger: + type: string + updated: + type: integer + version: + type: integer + type: object TypesIdentity: properties: email: @@ -5070,8 +6089,23 @@ components: $ref: '#/components/schemas/TypesRenameDetails' nullable: true type: array + total_commits: + type: integer type: object - TypesMembership: + TypesMembershipSpace: + properties: + added_by: + $ref: '#/components/schemas/TypesPrincipalInfo' + created: + type: integer + role: + $ref: '#/components/schemas/EnumMembershipRole' + space: + $ref: '#/components/schemas/TypesSpace' + updated: + type: integer + type: object + TypesMembershipUser: properties: added_by: $ref: '#/components/schemas/TypesPrincipalInfo' @@ -5084,6 +6118,15 @@ components: updated: type: integer type: object + TypesMergeResponse: + properties: + conflict_files: + items: + type: string + type: array + sha: + type: string + type: object TypesPath: properties: created: @@ -5103,6 +6146,35 @@ components: value: type: string type: object + TypesPipeline: + properties: + config_path: + type: string + created: + type: integer + default_branch: + type: string + description: + type: string + id: + type: integer + repo_id: + type: integer + repo_name: + type: string + repo_type: + $ref: '#/components/schemas/EnumScmType' + seq: + type: integer + space_id: + type: integer + uid: + type: string + updated: + type: integer + version: + type: integer + type: object TypesPrincipalInfo: properties: created: @@ -5293,6 +6365,23 @@ components: updated: type: integer type: object + TypesSecret: + properties: + created: + type: integer + description: + type: string + id: + type: integer + space_id: + type: integer + uid: + type: string + updated: + type: integer + version: + type: integer + type: object TypesServiceAccount: properties: admin: @@ -5348,6 +6437,7 @@ components: created_by: type: integer expires_at: + nullable: true type: integer grants: $ref: '#/components/schemas/EnumAccessGrant' diff --git a/web/src/utils/FileUtils.ts b/web/src/utils/FileUtils.ts index e3e4fe40c..397f45ff1 100644 --- a/web/src/utils/FileUtils.ts +++ b/web/src/utils/FileUtils.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from 'react' +import { useMemo } from 'react' import { pdfjs } from 'react-pdf' import { useAppContext } from 'AppContext' import type { RepoFileContent } from 'services/code' @@ -61,7 +61,6 @@ export function useFileContentViewerDecision({ const resourceData = resourceContent?.content as RepoFileContent const isFileTooLarge = resourceData?.size !== resourceData?.data_size const rawURL = `/code/api/v1/repos/${repoMetadata?.path}/+/raw/${resourcePath}?routingId=${routingId}&git_ref=${gitRef}` - return { category, @@ -77,21 +76,7 @@ export function useFileContentViewerDecision({ base64Data: resourceData?.data || '', rawURL } - }, []) // eslint-disable-line react-hooks/exhaustive-deps - - useEffect(() => { - switch (metadata.category) { - case FileCategory.SVG: - case FileCategory.PDF: - case FileCategory.IMAGE: - case FileCategory.AUDIO: - case FileCategory.VIDEO: - case FileCategory.TEXT: - break - default: - break - } - }, []) // eslint-disable-line react-hooks/exhaustive-deps + }, [resourceContent.content]) // eslint-disable-line react-hooks/exhaustive-deps return metadata } diff --git a/web/src/utils/Utils.ts b/web/src/utils/Utils.ts index 7ecf960cd..043a37397 100644 --- a/web/src/utils/Utils.ts +++ b/web/src/utils/Utils.ts @@ -9,6 +9,13 @@ export enum ACCESS_MODES { EDIT } +export enum PullRequestSection { + CONVERSATION = 'conversation', + COMMITS = 'commits', + FILES_CHANGED = 'changes', + CHECKS = 'checks' +} + export const LIST_FETCHING_LIMIT = 20 export const DEFAULT_DATE_FORMAT = 'MM/DD/YYYY hh:mm a' export const DEFAULT_BRANCH_NAME = 'main' @@ -97,6 +104,21 @@ export const displayDateTime = (value: number): string | null => { return value ? moment.unix(value / 1000).format(DEFAULT_DATE_FORMAT) : null } +export const timeDistance = (date1 = 0, date2 = 0) => { + let distance = Math.abs(date1 - date2) + + if (!distance) { + return '' + } + + const hours = Math.floor(distance / 3600000) + distance -= hours * 3600000 + const minutes = Math.floor(distance / 60000) + distance -= minutes * 60000 + const seconds = Math.floor(distance / 1000) + return `${hours ? hours + 'h ' : ''}${minutes ? minutes + 'm' : hours ? '0m' : ''} ${seconds}s` +} + const LOCALE = Intl.NumberFormat().resolvedOptions?.().locale || 'en-US' /** @@ -105,11 +127,13 @@ const LOCALE = Intl.NumberFormat().resolvedOptions?.().locale || 'en-US' * @param timeStyle Optional DateTimeFormat's `timeStyle` option. */ export function formatTime(timestamp: number | string, timeStyle = 'short'): string { - return new Intl.DateTimeFormat(LOCALE, { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore: TS built-in type for DateTimeFormat is not correct - timeStyle - }).format(new Date(timestamp)) + return timestamp + ? new Intl.DateTimeFormat(LOCALE, { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: TS built-in type for DateTimeFormat is not correct + timeStyle + }).format(new Date(timestamp)) + : '' } /** @@ -118,11 +142,13 @@ export function formatTime(timestamp: number | string, timeStyle = 'short'): str * @param dateStyle Optional DateTimeFormat's `dateStyle` option. */ export function formatDate(timestamp: number | string, dateStyle = 'medium'): string { - return new Intl.DateTimeFormat(LOCALE, { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore: TS built-in type for DateTimeFormat is not correct - dateStyle - }).format(new Date(timestamp)) + return timestamp + ? new Intl.DateTimeFormat(LOCALE, { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: TS built-in type for DateTimeFormat is not correct + dateStyle + }).format(new Date(timestamp)) + : '' } /** @@ -131,7 +157,7 @@ export function formatDate(timestamp: number | string, dateStyle = 'medium'): st * @returns Formatted string. */ export function formatNumber(num: number | bigint): string { - return new Intl.NumberFormat(LOCALE).format(num) + return num ? new Intl.NumberFormat(LOCALE).format(num) : '' } /** @@ -293,3 +319,19 @@ export function formatBytes(bytes: number, decimals = 2) { return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}` } + +export enum PullRequestCheckType { + EMPTY = '', + RAW = 'raw', + MARKDOWN = 'markdown' +} + +export function isInViewport(element: Element) { + const rect = element.getBoundingClientRect() + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ) +} diff --git a/web/src/utils/vars.scss b/web/src/utils/vars.scss index 0294615c9..a608df464 100644 --- a/web/src/utils/vars.scss +++ b/web/src/utils/vars.scss @@ -5,4 +5,6 @@ --nav-menu-width: 232px; --nav-background: #f2f6fb; --page-background: #fbfcfd; + + --page-height: var(--page-min-height, max(100vh, 800px)); } diff --git a/web/yarn.lock b/web/yarn.lock index 151e99132..3456f72e7 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -686,10 +686,10 @@ resolved "https://npm.pkg.github.com/download/@harness/design-system/1.4.0/b2a77f73696d71a53765c71efd0a5b28039fa1cf#b2a77f73696d71a53765c71efd0a5b28039fa1cf" integrity sha512-LuzuPEHPkE6xgIuXxn16RCCvPY1NDXF3o1JWlIjxmepoDTkgFuwnV1OhBdQftvAVBawJ5wJP10IIKUL161LdYg== -"@harness/icons@1.149.0": - version "1.149.0" - resolved "https://npm.pkg.github.com/download/@harness/icons/1.149.0/0f2e1faba8d5e1a651b5b8f2c03582d30702d81f#0f2e1faba8d5e1a651b5b8f2c03582d30702d81f" - integrity sha512-kfcGY7t+V1NoSRAoDJofZpKAY79UN+NI8XWf0OLYtRI1JnVF37ZyJgvJ3Vk4VgLbMElsPRXopcx9ADjCWz/RZw== +"@harness/icons@1.174.0": + version "1.174.0" + resolved "https://npm.pkg.github.com/download/@harness/icons/1.174.0/f0b22590cd34ddc2276d293c8c30afdb9174d62f#f0b22590cd34ddc2276d293c8c30afdb9174d62f" + integrity sha512-xAbVjP5u6LA3X8hhArjM5AiknyrPv5k+480c4ILrkrBKhDO6+W/sEh/wJjMOWRDxyxA9uU9RINOLt4QruqD8Vw== "@harness/jarvis@^0.12.0": version "0.12.0" @@ -6891,6 +6891,11 @@ ibm-openapi-validator@^0.16.0: validator "^11.0.0" yaml-js "^0.2.3" +iconoir-react@^6.11.0: + version "6.11.0" + resolved "https://registry.npmjs.org/iconoir-react/-/iconoir-react-6.11.0.tgz#a88d896148c8389138ec931ce7367f3abc6a7144" + integrity sha512-+1RgmEWh/9H0aYR2e8sDL5elDUYnkyOXO0E+RnwhkjhJY36Lge6r/9nv1n1FvDXoeFY48omkPUl8s/ciA0XOAg== + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"