1
0
Fork 0

Adding upstream version 3.10.8.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-18 09:37:23 +02:00
parent 37e9b6d587
commit 03bfe4079e
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
356 changed files with 28857 additions and 0 deletions

View file

@ -0,0 +1,85 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package filesystem
import (
"context"
"io"
"os"
"path/filepath"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type assetDriver struct {
nodeDriver
}
func newAssetDriver(content f3.Interface) generic.NodeDriverInterface {
n := newNodeDriver(content).(*nodeDriver)
a := &assetDriver{
nodeDriver: *n,
}
return a
}
func (o *assetDriver) getPath() string {
f := o.nodeDriver.content.(*f3.ReleaseAsset)
options := o.GetTreeDriver().(*treeDriver).options
return filepath.Join(options.Directory, "objects", f.SHA256[0:2], f.SHA256[2:4], f.SHA256)
}
func (o *assetDriver) save(ctx context.Context) {
assetFormat := o.nodeDriver.content.(*f3.ReleaseAsset)
objectsHelper := o.getF3Tree().GetObjectsHelper()
sha, tmpPath := objectsHelper.Save(assetFormat.DownloadFunc())
assetFormat.SHA256 = sha
path := o.getPath()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
panic(err)
}
if err := os.Rename(tmpPath, path); err != nil {
panic(err)
}
o.GetNode().Trace("%s %s", assetFormat.SHA256, path)
}
func (o *assetDriver) setDownloadFunc() {
f := o.nodeDriver.content.(*f3.ReleaseAsset)
f.DownloadFunc = func() io.ReadCloser {
f, err := os.Open(o.getPath())
if err != nil {
panic(err)
}
return f
}
}
func (o *assetDriver) Put(ctx context.Context) id.NodeID {
return o.upsert(ctx)
}
func (o *assetDriver) Patch(ctx context.Context) {
o.upsert(ctx)
}
func (o *assetDriver) upsert(ctx context.Context) id.NodeID {
assetFormat := o.nodeDriver.content.(*f3.ReleaseAsset)
if assetFormat.SHA256 != "" {
o.save(ctx)
}
o.setDownloadFunc()
return o.nodeDriver.upsert(ctx)
}
func (o *assetDriver) Get(ctx context.Context) bool {
if o.nodeDriver.Get(ctx) {
o.setDownloadFunc()
return true
}
return false
}

37
forges/filesystem/json.go Normal file
View file

@ -0,0 +1,37 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package filesystem
import (
"encoding/json"
"fmt"
"os"
)
func loadJSON(filename string, data any) {
bs, err := os.ReadFile(filename)
if err != nil {
panic(fmt.Errorf("ReadFile %w", err))
}
if err := json.Unmarshal(bs, data); err != nil {
panic(fmt.Errorf("Unmarshal %s %s %w", filename, string(bs), err))
}
}
func saveJSON(filename string, data any) {
f, err := os.Create(filename)
if err != nil {
panic(fmt.Errorf("Create %w", err))
}
defer f.Close()
bs, err := json.MarshalIndent(data, "", " ")
if err != nil {
panic(fmt.Errorf("MarshalIndent %w", err))
}
if _, err := f.Write(bs); err != nil {
panic(fmt.Errorf("Write %w", err))
}
}

16
forges/filesystem/main.go Normal file
View file

@ -0,0 +1,16 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package filesystem
import (
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
"code.forgejo.org/f3/gof3/v3/options"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
)
func init() {
f3_tree.RegisterForgeFactory(filesystem_options.Name, newTreeDriver)
options.RegisterFactory(filesystem_options.Name, newOptions)
}

249
forges/filesystem/node.go Normal file
View file

@ -0,0 +1,249 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package filesystem
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
)
type nodeDriver struct {
generic.NullDriver
content f3.Interface
}
func newNodeDriver(content f3.Interface) generic.NodeDriverInterface {
return &nodeDriver{
content: content.Clone(),
}
}
func (o *nodeDriver) SetNative(any) {}
func (o *nodeDriver) GetNativeID() string {
return o.GetNode().GetID().String()
}
func (o *nodeDriver) getBasePath() string {
options := o.GetTreeDriver().(*treeDriver).options
return options.Directory + o.GetNode().GetCurrentPath().String()
}
func (o *nodeDriver) getTree() generic.TreeInterface {
return o.GetNode().GetTree()
}
func (o *nodeDriver) getF3Tree() f3_tree.TreeInterface {
return o.getTree().(f3_tree.TreeInterface)
}
func (o *nodeDriver) isContainer() bool {
return o.getF3Tree().IsContainer(o.getKind())
}
func (o *nodeDriver) getKind() kind.Kind {
return o.GetNode().GetKind()
}
func (o *nodeDriver) IsNull() bool { return false }
func (o *nodeDriver) GetIDFromName(ctx context.Context, name string) id.NodeID {
switch o.getKind() {
case kind.KindRoot, f3_tree.KindProjects, f3_tree.KindUsers, f3_tree.KindOrganizations, f3_tree.KindRepositories:
default:
panic(fmt.Errorf("unxpected kind %s", o.getKind()))
}
for _, child := range o.GetNode().List(ctx) {
child.Get(ctx)
if child.ToFormat().GetName() == name {
return child.GetID()
}
}
return id.NilID
}
func (o *nodeDriver) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
node := o.GetNode()
node.Trace("%s '%s'", o.getKind(), node.GetID())
children := generic.NewChildrenSlice(0)
if o.getKind() == kind.KindRoot || page > 1 {
return children
}
basePath := o.getBasePath()
if !util.FileExists(basePath) {
return children
}
f3Tree := o.getF3Tree()
if !f3Tree.IsContainer(o.getKind()) {
return children
}
node.Trace("%d '%s'", page, basePath)
dirEntries, err := os.ReadDir(basePath)
if err != nil {
panic(fmt.Errorf("ReadDir %s %w", basePath, err))
}
for _, dirEntry := range dirEntries {
if !strings.HasSuffix(dirEntry.Name(), ".json") {
continue
}
node.Trace(" add %s", dirEntry.Name())
child := node.CreateChild(ctx)
i := strings.TrimSuffix(dirEntry.Name(), ".json")
childID := id.NewNodeID(i)
child.SetID(childID)
children = append(children, child)
}
return children
}
func (o *nodeDriver) Equals(context.Context, generic.NodeInterface) bool { panic("") }
func (o *nodeDriver) LookupMappedID(id id.NodeID) id.NodeID {
o.GetNode().Trace("%s", id)
return id
}
func (o *nodeDriver) hasJSON() bool {
kind := o.getKind()
if kind == f3_tree.KindForge {
return true
}
return !o.isContainer()
}
func (o *nodeDriver) Get(context.Context) bool {
o.GetNode().Trace("'%s' '%s'", o.getKind(), o.GetNode().GetID())
if !o.hasJSON() || o.GetNode().GetID() == id.NilID {
return true
}
filename := o.getBasePath() + ".json"
o.GetNode().Trace("'%s'", filename)
if !util.FileExists(filename) {
return false
}
f := o.NewFormat()
loadJSON(filename, f)
o.content = f
o.GetNode().Trace("%s %s id=%s", o.getKind(), filename, o.content.GetID())
idFilename := o.getBasePath() + ".id"
if !util.FileExists(idFilename) {
return true
}
mappedID, err := os.ReadFile(idFilename)
if err != nil {
panic(fmt.Errorf("Get %s %w", idFilename, err))
}
o.NullDriver.SetMappedID(id.NewNodeID(string(mappedID)))
return true
}
func (o *nodeDriver) SetMappedID(mapped id.NodeID) {
o.NullDriver.SetMappedID(mapped)
o.saveMappedID()
}
func (o *nodeDriver) saveMappedID() {
k := o.getKind()
switch k {
case kind.KindRoot, f3_tree.KindForge:
return
}
if o.isContainer() {
return
}
mappedID := o.GetMappedID()
if mappedID == id.NilID {
return
}
basePath := o.getBasePath()
idFilename := basePath + ".id"
o.Trace("%s", idFilename)
if err := os.WriteFile(idFilename, []byte(o.GetMappedID().String()), 0o644); err != nil {
panic(fmt.Errorf("%s %w", idFilename, err))
}
}
func (o *nodeDriver) Put(ctx context.Context) id.NodeID {
return o.upsert(ctx)
}
func (o *nodeDriver) Patch(ctx context.Context) {
o.upsert(ctx)
}
func (o *nodeDriver) upsert(context.Context) id.NodeID {
i := o.GetNode().GetID()
o.GetNode().Trace("%s %s", o.getKind(), i)
o.content.SetID(i.String())
if !o.hasJSON() || i == id.NilID {
return i
}
basePath := o.getBasePath()
dirname := filepath.Dir(basePath)
if !util.FileExists(dirname) {
if err := os.MkdirAll(dirname, 0o777); err != nil {
panic(fmt.Errorf("MakeDirAll %s %w", dirname, err))
}
}
saveJSON(basePath+".json", o.content)
o.saveMappedID()
return i
}
func (o *nodeDriver) Delete(context.Context) {
if o.isContainer() {
return
}
basePath := o.getBasePath()
if util.FileExists(basePath) {
if err := os.RemoveAll(basePath); err != nil {
panic(fmt.Errorf("RemoveAll %s %w", basePath, err))
}
}
for _, ext := range []string{".id", ".json"} {
jsonFilename := basePath + ext
if util.FileExists(jsonFilename) {
if err := os.Remove(jsonFilename); err != nil {
panic(fmt.Errorf("RemoveAll %s %w", basePath, err))
}
}
}
o.content = o.NewFormat()
}
func (o *nodeDriver) NewFormat() f3.Interface {
return o.getTree().(f3_tree.TreeInterface).NewFormat(o.getKind())
}
func (o *nodeDriver) FromFormat(content f3.Interface) {
o.content = content
}
func (o *nodeDriver) ToFormat() f3.Interface {
return o.content.Clone()
}
func (o *nodeDriver) String() string {
return o.content.GetID()
}

View file

@ -0,0 +1,16 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package filesystem
import (
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
"code.forgejo.org/f3/gof3/v3/options"
)
func newOptions() options.Interface {
o := &filesystem_options.Options{}
o.SetName(filesystem_options.Name)
return o
}

View file

@ -0,0 +1,7 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package options
const Name = "filesystem"

View file

@ -0,0 +1,60 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package options
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/options"
"code.forgejo.org/f3/gof3/v3/options/logger"
"github.com/urfave/cli/v3"
)
type Options struct {
options.Options
logger.OptionsLogger
Validation bool
Directory string
}
func (o *Options) GetURL() string { return o.Directory }
func (o *Options) SetURL(url string) { o.Directory = url }
func (o *Options) FromFlags(ctx context.Context, c *cli.Command, prefix string) {
o.Directory = c.String(forgeDirectoryOption(prefix))
if o.Directory == "" {
panic(fmt.Errorf("--%s is required", forgeDirectoryOption(prefix)))
}
o.Validation = c.Bool(forgeValidationOption(prefix))
}
func forgeValidationOption(direction string) string {
return direction + "-validation"
}
func forgeDirectoryOption(direction string) string {
return direction + "-directory"
}
func (o *Options) GetFlags(prefix, category string) []cli.Flag {
flags := make([]cli.Flag, 0, 10)
flags = append(flags, &cli.BoolFlag{
Name: forgeValidationOption(prefix),
Usage: "validate the JSON files against F3 JSON schemas",
Category: prefix,
})
flags = append(flags, &cli.StringFlag{
Name: forgeDirectoryOption(prefix),
Usage: "path to the F3 archive",
Category: prefix,
})
return flags
}

View file

@ -0,0 +1,68 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package filesystem
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
helper_pullrequest "code.forgejo.org/f3/gof3/v3/forges/helpers/pullrequest"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type pullRequestDriver struct {
nodeDriver
h helper_pullrequest.Interface
}
func newPullRequestDriver(ctx context.Context, content f3.Interface) generic.NodeDriverInterface {
n := newNodeDriver(content).(*nodeDriver)
r := &pullRequestDriver{
nodeDriver: *n,
}
r.h = helper_pullrequest.NewHelper(ctx, r)
return r
}
func (o *pullRequestDriver) GetPullRequestPushRefs() []string {
return []string{fmt.Sprintf("refs/f3/%s/head", o.GetNativeID())}
}
func (o *pullRequestDriver) GetPullRequestRef() string {
return fmt.Sprintf("refs/f3/%s/head", o.GetNativeID())
}
func (o *pullRequestDriver) GetPullRequestHead() string {
f := o.nodeDriver.content.(*f3.PullRequest)
return f.Head.Ref
}
func (o *pullRequestDriver) SetFetchFunc(fetchFunc func(ctx context.Context, url, ref string)) {
f := o.nodeDriver.content.(*f3.PullRequest)
f.FetchFunc = fetchFunc
}
func (o *pullRequestDriver) Put(ctx context.Context) id.NodeID {
return o.upsert(ctx)
}
func (o *pullRequestDriver) Patch(ctx context.Context) {
o.upsert(ctx)
}
func (o *pullRequestDriver) upsert(ctx context.Context) id.NodeID {
o.nodeDriver.upsert(ctx)
return o.h.Upsert(ctx)
}
func (o *pullRequestDriver) Get(ctx context.Context) bool {
if o.nodeDriver.Get(ctx) {
o.h.Get(ctx)
return true
}
return false
}

View file

@ -0,0 +1,97 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package filesystem
import (
"context"
"os"
"code.forgejo.org/f3/gof3/v3/f3"
helpers_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/repository"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
)
type repositoryDriver struct {
nodeDriver
h helpers_repository.Interface
}
func newRepositoryDriver(content f3.Interface) generic.NodeDriverInterface {
n := newNodeDriver(content).(*nodeDriver)
r := &repositoryDriver{
nodeDriver: *n,
}
r.h = helpers_repository.NewHelper(r)
return r
}
func (o *repositoryDriver) ToFormat() f3.Interface {
from := o.GetRepositoryURL()
o.Trace("%s", from)
f := o.nodeDriver.ToFormat().(*f3.Repository)
f.FetchFunc = func(ctx context.Context, destination string, internalRefs []string) {
o.Trace("git clone %s %s", from, destination)
helpers_repository.GitMirror(ctx, o, from, destination, internalRefs)
}
return f
}
func (o *repositoryDriver) GetHelper() any {
return o.h
}
func (o *repositoryDriver) SetFetchFunc(fetchFunc func(ctx context.Context, destination string, internalRefs []string)) {
f := o.nodeDriver.content.(*f3.Repository)
f.FetchFunc = fetchFunc
}
func (o *repositoryDriver) GetRepositoryURL() string {
return o.getBasePath()
}
func (o *repositoryDriver) GetRepositoryPushURL() string {
return o.getBasePath()
}
func (o *repositoryDriver) GetRepositoryInternalRefs() []string {
return []string{}
}
func (o *repositoryDriver) ensureRepository(ctx context.Context, directory string) {
if !util.FileExists(directory) {
if err := os.MkdirAll(directory, 0o700); err != nil {
panic(err)
}
util.Command(ctx, o, "git", "-C", directory, "init", "--bare")
}
}
func (o *repositoryDriver) Put(ctx context.Context) id.NodeID {
return o.upsert(ctx)
}
func (o *repositoryDriver) Patch(ctx context.Context) {
o.upsert(ctx)
}
func (o *repositoryDriver) upsert(ctx context.Context) id.NodeID {
o.nodeDriver.upsert(ctx)
repoDir := o.GetRepositoryPushURL()
o.GetNode().Trace("%s %s", repoDir, o.GetNode().GetID())
o.ensureRepository(ctx, repoDir)
return o.h.Upsert(ctx, o.content.(*f3.Repository))
}
func (o *repositoryDriver) Get(ctx context.Context) bool {
o.nodeDriver.Get(ctx)
f := o.nodeDriver.content.(*f3.Repository)
f.SetID(o.GetNode().GetID().String())
repoDir := o.GetRepositoryURL()
o.GetNode().Trace("%s", repoDir)
o.h.Get(ctx)
return true
}

View file

@ -0,0 +1,23 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package tests
import (
"testing"
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
"code.forgejo.org/f3/gof3/v3/logger"
"code.forgejo.org/f3/gof3/v3/options"
)
func newTestOptions(t *testing.T) options.Interface {
t.Helper()
o := options.GetFactory(filesystem_options.Name)().(*filesystem_options.Options)
o.Directory = t.TempDir()
l := logger.NewLogger()
l.SetLevel(logger.Trace)
o.OptionsLogger.SetLogger(l)
return o
}

View file

@ -0,0 +1,14 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package tests
import (
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
)
func init() {
tests_forge.RegisterFactory(filesystem_options.Name, newForgeTest)
}

View file

@ -0,0 +1,27 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package tests
import (
"testing"
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
"code.forgejo.org/f3/gof3/v3/options"
tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
)
type forgeTest struct {
tests_forge.Base
}
func (o *forgeTest) NewOptions(t *testing.T) options.Interface {
return newTestOptions(t)
}
func newForgeTest() tests_forge.Interface {
t := &forgeTest{}
t.SetName(filesystem_options.Name)
return t
}

45
forges/filesystem/tree.go Normal file
View file

@ -0,0 +1,45 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package filesystem
import (
"context"
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
"code.forgejo.org/f3/gof3/v3/kind"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type treeDriver struct {
generic.NullTreeDriver
options *filesystem_options.Options
}
func newTreeDriver(tree generic.TreeInterface, anyOptions any) generic.TreeDriverInterface {
driver := &treeDriver{
options: anyOptions.(*filesystem_options.Options),
}
driver.SetTree(tree)
driver.Init()
return driver
}
func (o *treeDriver) AllocateID() bool { return false }
func (o *treeDriver) Factory(ctx context.Context, k kind.Kind) generic.NodeDriverInterface {
content := o.GetTree().(f3_tree.TreeInterface).NewFormat(k)
switch k {
case f3_tree.KindRepository:
return newRepositoryDriver(content)
case f3_tree.KindAsset:
return newAssetDriver(content)
case f3_tree.KindPullRequest:
return newPullRequestDriver(ctx, content)
default:
return newNodeDriver(content)
}
}

178
forges/forgejo/asset.go Normal file
View file

@ -0,0 +1,178 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"io"
"net/http"
"os"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type asset struct {
common
forgejoAsset *forgejo_sdk.Attachment
sha string
contentType string
downloadFunc f3.DownloadFuncType
}
var _ f3_tree.ForgeDriverInterface = &pullRequest{}
func newAsset() generic.NodeDriverInterface {
return &asset{}
}
func (o *asset) SetNative(asset any) {
o.forgejoAsset = asset.(*forgejo_sdk.Attachment)
}
func (o *asset) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoAsset.ID)
}
func (o *asset) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *asset) ToFormat() f3.Interface {
if o.forgejoAsset == nil {
return o.NewFormat()
}
if o.sha == "" {
// the API does not provide the SHA256, save the asset in a temporary file
// to get it
objectsHelper := o.getF3Tree().GetObjectsHelper()
o.Trace("download from %s", o.forgejoAsset.DownloadURL)
req, err := http.NewRequest("GET", o.forgejoAsset.DownloadURL, nil)
if err != nil {
panic(err)
}
httpClient := o.getNewMigrationHTTPClient()()
resp, err := httpClient.Do(req)
if err != nil {
panic(fmt.Errorf("while downloading %s %w", o.forgejoAsset.DownloadURL, err))
}
sha, path := objectsHelper.Save(resp.Body)
o.sha = sha
o.downloadFunc = func() io.ReadCloser {
o.Trace("download %s from copy stored in temporary file %s", o.forgejoAsset.DownloadURL, path)
f, err := os.Open(path)
if err != nil {
panic(err)
}
return f
}
}
return &f3.ReleaseAsset{
Common: f3.NewCommon(o.GetNativeID()),
Name: o.forgejoAsset.Name,
Size: o.forgejoAsset.Size,
ContentType: o.contentType,
DownloadCount: o.forgejoAsset.DownloadCount,
SHA256: o.sha,
DownloadURL: o.forgejoAsset.DownloadURL,
Created: o.forgejoAsset.Created,
DownloadFunc: o.downloadFunc,
}
}
func (o *asset) FromFormat(content f3.Interface) {
asset := content.(*f3.ReleaseAsset)
o.forgejoAsset = &forgejo_sdk.Attachment{
ID: util.ParseInt(asset.GetID()),
Name: asset.Name,
Size: asset.Size,
DownloadCount: asset.DownloadCount,
Created: asset.Created,
DownloadURL: asset.DownloadURL,
}
o.sha = asset.SHA256
o.contentType = asset.ContentType
o.downloadFunc = asset.DownloadFunc
}
func (o *asset) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
release := f3_tree.GetReleaseID(o.GetNode())
asset, resp, err := o.getClient().GetReleaseAttachment(owner, project, release, node.GetID().Int64())
if resp.StatusCode == 404 {
return false
}
if err != nil {
panic(fmt.Errorf("asset %v %w", o, err))
}
o.forgejoAsset = asset
return true
}
func (o *asset) Put(ctx context.Context) id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
release := f3_tree.GetReleaseID(o.GetNode())
asset, _, err := o.getClient().CreateReleaseAttachment(owner, project, release, o.downloadFunc(), o.forgejoAsset.Name)
if err != nil {
panic(err)
}
o.forgejoAsset = asset
o.sha = ""
return id.NewNodeID(o.GetNativeID())
}
func (o *asset) Patch(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
release := f3_tree.GetReleaseID(o.GetNode())
_, _, err := o.getClient().EditReleaseAttachment(owner, project, release, node.GetID().Int64(), forgejo_sdk.EditAttachmentOptions{
Name: o.forgejoAsset.Name,
})
if err != nil {
panic(err)
}
}
func (o *asset) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
release := f3_tree.GetReleaseID(o.GetNode())
_, err := o.getClient().DeleteReleaseAttachment(owner, project, release, node.GetID().Int64())
if err != nil {
panic(err)
}
}

43
forges/forgejo/assets.go Normal file
View file

@ -0,0 +1,43 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type assets struct {
container
}
func (o *assets) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
var err error
var forgejoAssets []*forgejo_sdk.Attachment
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
release := f3_tree.GetReleaseID(o.GetNode())
forgejoAssets, _, err = o.getClient().ListReleaseAttachments(owner, project, release, forgejo_sdk.ListReleaseAttachmentsOptions{
ListOptions: forgejo_sdk.ListOptions{Page: page, PageSize: pageSize},
})
if err != nil {
panic(fmt.Errorf("error while listing release attachments: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoAssets...)...)
}
func newAssets() generic.NodeDriverInterface {
return &assets{}
}

140
forges/forgejo/comment.go Normal file
View file

@ -0,0 +1,140 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type comment struct {
common
forgejoComment *forgejo_sdk.Comment
}
var _ f3_tree.ForgeDriverInterface = &pullRequest{}
func newComment() generic.NodeDriverInterface {
return &comment{}
}
func (o *comment) SetNative(comment any) {
o.forgejoComment = comment.(*forgejo_sdk.Comment)
}
func (o *comment) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoComment.ID)
}
func (o *comment) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *comment) ToFormat() f3.Interface {
if o.forgejoComment == nil {
return o.NewFormat()
}
return &f3.Comment{
Common: f3.NewCommon(o.GetNativeID()),
PosterID: f3_tree.NewUserReference(o.forgejoComment.Poster.ID),
Content: o.forgejoComment.Body,
Created: o.forgejoComment.Created,
Updated: o.forgejoComment.Updated,
}
}
func (o *comment) FromFormat(content f3.Interface) {
comment := content.(*f3.Comment)
o.forgejoComment = &forgejo_sdk.Comment{
ID: util.ParseInt(comment.GetID()),
Poster: &forgejo_sdk.User{
ID: comment.PosterID.GetIDAsInt(),
},
Body: comment.Content,
Created: comment.Created,
Updated: comment.Updated,
}
}
func (o *comment) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
comment, resp, err := o.getClient().GetIssueComment(owner, project, node.GetID().Int64())
if resp.StatusCode == 404 {
return false
}
if err != nil {
panic(fmt.Errorf("comment %v %w", o, err))
}
o.forgejoComment = comment
return true
}
func (o *comment) Put(ctx context.Context) id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
commentable := f3_tree.GetCommentableID(o.GetNode())
o.maybeSudoID(ctx, o.forgejoComment.Poster.ID)
defer o.notSudo()
comment, _, err := o.getClient().CreateIssueComment(owner, project, commentable, forgejo_sdk.CreateIssueCommentOption{
Body: o.forgejoComment.Body,
})
if err != nil {
panic(err)
}
o.forgejoComment = comment
return id.NewNodeID(o.GetNativeID())
}
func (o *comment) Patch(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
_, _, err := o.getClient().EditIssueComment(owner, project, node.GetID().Int64(), forgejo_sdk.EditIssueCommentOption{
Body: o.forgejoComment.Body,
})
if err != nil {
panic(err)
}
}
func (o *comment) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
resp, err := o.getClient().DeleteIssueComment(owner, project, node.GetID().Int64())
if resp.StatusCode != 204 {
panic(fmt.Errorf("unexpected status code deleting %s %d %v", node.GetID().String(), resp.StatusCode, resp))
}
if err != nil {
panic(err)
}
}

View file

@ -0,0 +1,44 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type comments struct {
container
}
func (o *comments) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
children := generic.NewChildrenSlice(0)
if page > 1 {
return children
}
var err error
var forgejoComments []*forgejo_sdk.Comment
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
commentable := f3_tree.GetCommentableID(o.GetNode())
forgejoComments, _, err = o.getClient().ListIssueComments(owner, project, commentable, forgejo_sdk.ListIssueCommentOptions{})
if err != nil {
panic(fmt.Errorf("error while listing comments: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoComments...)...)
}
func newComments() generic.NodeDriverInterface {
return &comments{}
}

120
forges/forgejo/common.go Normal file
View file

@ -0,0 +1,120 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
options_http "code.forgejo.org/f3/gof3/v3/options/http"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
"github.com/hashicorp/go-version"
)
func maybeMilestoneID(milestone *forgejo_sdk.Milestone) *int64 {
if milestone != nil {
id := milestone.ID
return &id
}
return nil
}
func labelListToIDs(labels []*forgejo_sdk.Label) []int64 {
ids := make([]int64, 0, len(labels))
for _, label := range labels {
ids = append(ids, label.ID)
}
return ids
}
type common struct {
generic.NullDriver
}
func (o *common) GetHelper() any {
panic("not implemented")
}
func (o *common) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
return generic.NewChildrenSlice(0)
}
func (o *common) getTree() generic.TreeInterface {
return o.GetNode().GetTree()
}
func (o *common) getForge() *forge {
return o.getTree().GetRoot().GetChild(id.NewNodeID(f3_tree.KindForge)).GetDriver().(*forge)
}
func (o *common) getPageSize() int {
return o.getTreeDriver().GetPageSize()
}
func (o *common) getF3Tree() f3_tree.TreeInterface {
return o.getTree().(f3_tree.TreeInterface)
}
func (o *common) getKind() kind.Kind {
return o.GetNode().GetKind()
}
func (o *common) getChildDriver(kind kind.Kind) generic.NodeDriverInterface {
return o.GetNode().GetChild(id.NewNodeID(kind)).GetDriver()
}
func (o *common) getProject() *project {
return f3_tree.GetProject(o.GetNode()).GetDriver().(*project)
}
func (o *common) isContainer() bool {
return o.getF3Tree().IsContainer(o.getKind())
}
func (o *common) getURL() string {
return o.getTreeDriver().options.GetURL()
}
func (o *common) getPushURL() string {
return o.getTreeDriver().options.GetPushURL()
}
func (o *common) getNewMigrationHTTPClient() options_http.NewMigrationHTTPClientFun {
return o.getTreeDriver().options.GetNewMigrationHTTPClient()
}
func (o *common) getTreeDriver() *treeDriver {
return o.GetTreeDriver().(*treeDriver)
}
func (o *common) getIsAdmin() bool {
return o.getTreeDriver().GetIsAdmin()
}
func (o *common) getClient() *forgejo_sdk.Client {
return o.getTreeDriver().GetClient()
}
func (o *common) maybeSudoName(name string) {
o.getTreeDriver().MaybeSudoName(name)
}
func (o *common) maybeSudoID(ctx context.Context, id int64) {
o.getTreeDriver().maybeSudoID(ctx, id)
}
func (o *common) notSudo() {
o.getTreeDriver().NotSudo()
}
func (o *common) getVersion() *version.Version {
return o.getTreeDriver().GetVersion()
}
func (o *common) IsNull() bool { return false }

View file

@ -0,0 +1,43 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
)
type container struct {
common
}
func (o *container) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *container) ToFormat() f3.Interface {
return o.NewFormat()
}
func (o *container) FromFormat(content f3.Interface) {
}
func (o *container) Get(context.Context) bool { return true }
func (o *container) Put(ctx context.Context) id.NodeID {
return o.upsert(ctx)
}
func (o *container) Patch(ctx context.Context) {
o.upsert(ctx)
}
func (o *container) upsert(context.Context) id.NodeID {
return id.NewNodeID(o.getKind())
}

92
forges/forgejo/forge.go Normal file
View file

@ -0,0 +1,92 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"strings"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
)
type ownerInfo struct {
kind kind.Kind
name string
}
type forge struct {
common
ownersInfo map[string]ownerInfo
}
func newForge() generic.NodeDriverInterface {
return &forge{
ownersInfo: make(map[string]ownerInfo),
}
}
func (o *forge) getOwnersKind(ctx context.Context, id string) kind.Kind {
return o.getOwnersInfo(ctx, id).kind
}
func (o *forge) getOwnersName(ctx context.Context, id string) string {
return o.getOwnersInfo(ctx, id).name
}
func (o *forge) getOwnersInfo(ctx context.Context, id string) ownerInfo {
info, ok := o.ownersInfo[id]
if !ok {
user, _, err := o.getClient().GetUserByID(util.ParseInt(id))
if err != nil {
if strings.Contains(err.Error(), "user not found") {
organizations := o.getChildDriver(f3_tree.KindOrganizations).(*organizations)
info.name = organizations.getNameFromID(ctx, util.ParseInt(id))
if info.name != "" {
info.kind = f3_tree.KindOrganizations
} else {
panic(fmt.Errorf("%v does not match any user or organization", id))
}
} else {
panic(fmt.Errorf("GetUserByID(%s) failed %w", id, err))
}
} else {
info.kind = f3_tree.KindUsers
info.name = user.UserName
}
o.ownersInfo[id] = info
}
return info
}
func (o *forge) getOwnersPath(ctx context.Context, id string) f3_tree.Path {
return f3_tree.NewPathFromString("/").SetForge().SetOwners(o.getOwnersKind(ctx, id))
}
func (o *forge) Equals(context.Context, generic.NodeInterface) bool { return true }
func (o *forge) Get(context.Context) bool { return true }
func (o *forge) Put(context.Context) id.NodeID { return id.NewNodeID("forge") }
func (o *forge) Patch(context.Context) {}
func (o *forge) Delete(context.Context) {}
func (o *forge) NewFormat() f3.Interface { return &f3.Forge{} }
func (o *forge) FromFormat(f3.Interface) {}
func (o *forge) ToFormat() f3.Interface {
return &f3.Forge{
Common: f3.NewCommon("forge"),
URL: o.String(),
}
}
func (o *forge) String() string {
options := o.GetTreeDriver().(*treeDriver).options
return options.ForgeAuth.GetURL()
}

211
forges/forgejo/issue.go Normal file
View file

@ -0,0 +1,211 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type issue struct {
common
forgejoIssue *forgejo_sdk.Issue
}
var _ f3_tree.ForgeDriverInterface = &pullRequest{}
func newIssue() generic.NodeDriverInterface {
return &issue{}
}
func (o *issue) SetNative(issue any) {
o.forgejoIssue = issue.(*forgejo_sdk.Issue)
}
func (o *issue) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoIssue.Index)
}
func (o *issue) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *issue) ToFormat() f3.Interface {
if o.forgejoIssue == nil {
return o.NewFormat()
}
milestone := &f3.Reference{}
if o.forgejoIssue.Milestone != nil {
milestone = f3_tree.NewIssueMilestoneReference(o.forgejoIssue.Milestone.ID)
}
assignees := make([]*f3.Reference, 0, len(o.forgejoIssue.Assignees))
for _, assignee := range o.forgejoIssue.Assignees {
assignees = append(assignees, f3_tree.NewUserReference(assignee.ID))
}
labels := make([]*f3.Reference, 0, len(o.forgejoIssue.Labels))
for _, label := range o.forgejoIssue.Labels {
labels = append(labels, f3_tree.NewIssueLabelReference(label.ID))
}
return &f3.Issue{
Title: o.forgejoIssue.Title,
Common: f3.NewCommon(o.GetNativeID()),
PosterID: f3_tree.NewUserReference(o.forgejoIssue.Poster.ID),
Assignees: assignees,
Labels: labels,
Content: o.forgejoIssue.Body,
Milestone: milestone,
State: string(o.forgejoIssue.State),
Created: o.forgejoIssue.Created,
Updated: o.forgejoIssue.Updated,
Closed: o.forgejoIssue.Closed,
IsLocked: o.forgejoIssue.IsLocked,
}
}
func (o *issue) FromFormat(content f3.Interface) {
issue := content.(*f3.Issue)
var milestone *forgejo_sdk.Milestone
if issue.Milestone != nil {
milestone = &forgejo_sdk.Milestone{
ID: issue.Milestone.GetIDAsInt(),
}
}
o.forgejoIssue = &forgejo_sdk.Issue{
Title: issue.Title,
Index: util.ParseInt(issue.GetID()),
Poster: &forgejo_sdk.User{
ID: issue.PosterID.GetIDAsInt(),
},
Body: issue.Content,
Milestone: milestone,
State: forgejo_sdk.StateType(issue.State),
Created: issue.Created,
Updated: issue.Updated,
Closed: issue.Closed,
IsLocked: issue.IsLocked,
}
assignees := make([]*forgejo_sdk.User, 0, 5)
for _, assignee := range issue.Assignees {
assignees = append(assignees, &forgejo_sdk.User{ID: assignee.GetIDAsInt()})
}
o.forgejoIssue.Assignees = assignees
labels := make([]*forgejo_sdk.Label, 0, 5)
for _, label := range issue.Labels {
labels = append(labels, &forgejo_sdk.Label{ID: label.GetIDAsInt()})
}
o.forgejoIssue.Labels = labels
}
func (o *issue) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
issue, resp, err := o.getClient().GetIssue(owner, project, node.GetID().Int64())
if resp.StatusCode == 404 {
return false
}
if err != nil {
panic(fmt.Errorf("issue %v %w", o, err))
}
o.forgejoIssue = issue
return true
}
func usersToNames(ctx context.Context, tree f3_tree.TreeInterface, users []*forgejo_sdk.User) []string {
names := make([]string, 0, len(users))
for _, user := range users {
names = append(names, f3_tree.GetUsernameFromID(ctx, tree, user.ID))
}
return names
}
func (o *issue) Put(ctx context.Context) id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
o.maybeSudoID(ctx, o.forgejoIssue.Poster.ID)
defer o.notSudo()
createIssueOption := forgejo_sdk.CreateIssueOption{
Title: o.forgejoIssue.Title,
Body: o.forgejoIssue.Body,
Assignees: usersToNames(ctx, node.GetTree().(f3_tree.TreeInterface), o.forgejoIssue.Assignees),
Labels: labelListToIDs(o.forgejoIssue.Labels),
Closed: o.forgejoIssue.State == forgejo_sdk.StateClosed,
}
if milestone := maybeMilestoneID(o.forgejoIssue.Milestone); milestone != nil {
createIssueOption.Milestone = *milestone
}
issue, _, err := o.getClient().CreateIssue(owner, project, createIssueOption)
if err != nil {
panic(err)
}
o.forgejoIssue = issue
return id.NewNodeID(o.GetNativeID())
}
func (o *issue) Patch(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
o.maybeSudoID(ctx, o.forgejoIssue.Poster.ID)
defer o.notSudo()
_, _, err := o.getClient().EditIssue(owner, project, node.GetID().Int64(), forgejo_sdk.EditIssueOption{
Title: o.forgejoIssue.Title,
Body: &o.forgejoIssue.Body,
State: &o.forgejoIssue.State,
Milestone: maybeMilestoneID(o.forgejoIssue.Milestone),
Assignees: usersToNames(ctx, node.GetTree().(f3_tree.TreeInterface), o.forgejoIssue.Assignees),
})
if err != nil {
panic(fmt.Errorf("EditIssue %v %w", o, err))
}
_, _, err = o.getClient().ReplaceIssueLabels(owner, project, node.GetID().Int64(), forgejo_sdk.IssueLabelsOption{
Labels: labelListToIDs(o.forgejoIssue.Labels),
})
if err != nil {
panic(fmt.Errorf("ReplaceIssueLabels %v %w", o, err))
}
}
func (o *issue) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
_, err := o.getClient().DeleteIssue(owner, project, node.GetID().Int64())
if err != nil {
panic(err)
}
}

47
forges/forgejo/issues.go Normal file
View file

@ -0,0 +1,47 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type issues struct {
container
}
func (o *issues) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
var err error
var forgejoIssues []*forgejo_sdk.Issue
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
forgejoIssues, resp, err := o.getClient().ListRepoIssues(owner, project, forgejo_sdk.ListIssueOption{
ListOptions: forgejo_sdk.ListOptions{Page: page, PageSize: pageSize},
State: forgejo_sdk.StateAll,
Type: forgejo_sdk.IssueTypeIssue,
})
if resp.StatusCode == 404 {
return generic.NewChildrenSlice(0)
}
if err != nil {
panic(fmt.Errorf("error while listing issues: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoIssues...)...)
}
func newIssues() generic.NodeDriverInterface {
return &issues{}
}

133
forges/forgejo/label.go Normal file
View file

@ -0,0 +1,133 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type label struct {
common
forgejoLabel *forgejo_sdk.Label
}
var _ f3_tree.ForgeDriverInterface = &pullRequest{}
func newLabel() generic.NodeDriverInterface {
return &label{}
}
func (o *label) SetNative(label any) {
o.forgejoLabel = label.(*forgejo_sdk.Label)
}
func (o *label) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoLabel.ID)
}
func (o *label) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *label) ToFormat() f3.Interface {
if o.forgejoLabel == nil {
return o.NewFormat()
}
return &f3.Label{
Common: f3.NewCommon(o.GetNativeID()),
Name: o.forgejoLabel.Name,
Description: o.forgejoLabel.Description,
Color: o.forgejoLabel.Color,
}
}
func (o *label) FromFormat(content f3.Interface) {
label := content.(*f3.Label)
o.forgejoLabel = &forgejo_sdk.Label{
ID: util.ParseInt(label.GetID()),
Name: label.Name,
Description: label.Description,
Color: label.Color,
}
}
func (o *label) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
label, resp, err := o.getClient().GetRepoLabel(owner, project, node.GetID().Int64())
if resp.StatusCode == 404 {
return false
}
if err != nil {
panic(fmt.Errorf("label %v %w", o, err))
}
o.forgejoLabel = label
return true
}
func (o *label) Put(ctx context.Context) id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
label, _, err := o.getClient().CreateLabel(owner, project, forgejo_sdk.CreateLabelOption{
Name: o.forgejoLabel.Name,
Color: o.forgejoLabel.Color,
Description: o.forgejoLabel.Description,
})
if err != nil {
panic(err)
}
o.forgejoLabel = label
return id.NewNodeID(o.GetNativeID())
}
func (o *label) Patch(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
_, _, err := o.getClient().EditLabel(owner, project, node.GetID().Int64(), forgejo_sdk.EditLabelOption{
Name: &o.forgejoLabel.Name,
Color: &o.forgejoLabel.Color,
Description: &o.forgejoLabel.Description,
})
if err != nil {
panic(err)
}
}
func (o *label) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
_, err := o.getClient().DeleteLabel(owner, project, node.GetID().Int64())
if err != nil {
panic(err)
}
}

42
forges/forgejo/labels.go Normal file
View file

@ -0,0 +1,42 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type labels struct {
container
}
func (o *labels) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
var err error
var forgejoLabels []*forgejo_sdk.Label
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
forgejoLabels, _, err = o.getClient().ListRepoLabels(owner, project, forgejo_sdk.ListLabelsOptions{
ListOptions: forgejo_sdk.ListOptions{Page: page, PageSize: pageSize},
})
if err != nil {
panic(fmt.Errorf("error while listing labels: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoLabels...)...)
}
func newLabels() generic.NodeDriverInterface {
return &labels{}
}

19
forges/forgejo/main.go Normal file
View file

@ -0,0 +1,19 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
forgejo_options "code.forgejo.org/f3/gof3/v3/forges/forgejo/options"
"code.forgejo.org/f3/gof3/v3/options"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
)
func init() {
f3_tree.RegisterForgeFactory(forgejo_options.Name, newTreeDriver)
options.RegisterFactory(forgejo_options.Name, newOptions)
f3_tree.RegisterForgeFactory(forgejo_options.NameAliasGitea, newTreeDriver)
options.RegisterFactory(forgejo_options.NameAliasGitea, newOptions)
}

158
forges/forgejo/milestone.go Normal file
View file

@ -0,0 +1,158 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"time"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type milestone struct {
common
forgejoMilestone *forgejo_sdk.Milestone
}
var _ f3_tree.ForgeDriverInterface = &pullRequest{}
func newMilestone() generic.NodeDriverInterface {
return &milestone{}
}
func (o *milestone) SetNative(milestone any) {
o.forgejoMilestone = milestone.(*forgejo_sdk.Milestone)
}
func (o *milestone) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoMilestone.ID)
}
func (o *milestone) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *milestone) ToFormat() f3.Interface {
if o.forgejoMilestone == nil {
return o.NewFormat()
}
createdAT := time.Time{}
var updatedAT *time.Time
if o.forgejoMilestone.Closed != nil {
createdAT = *o.forgejoMilestone.Closed
updatedAT = o.forgejoMilestone.Closed
}
if !o.forgejoMilestone.Created.IsZero() {
createdAT = o.forgejoMilestone.Created
}
if o.forgejoMilestone.Updated != nil && !o.forgejoMilestone.Updated.IsZero() {
updatedAT = o.forgejoMilestone.Updated
}
return &f3.Milestone{
Common: f3.NewCommon(o.GetNativeID()),
Title: o.forgejoMilestone.Title,
Description: o.forgejoMilestone.Description,
Deadline: o.forgejoMilestone.Deadline,
Created: createdAT,
Updated: updatedAT,
Closed: o.forgejoMilestone.Closed,
State: string(o.forgejoMilestone.State),
}
}
func (o *milestone) FromFormat(content f3.Interface) {
milestone := content.(*f3.Milestone)
o.forgejoMilestone = &forgejo_sdk.Milestone{
ID: util.ParseInt(milestone.GetID()),
Title: milestone.Title,
Description: milestone.Description,
State: forgejo_sdk.StateType(milestone.State),
Created: milestone.Created,
Updated: milestone.Updated,
Closed: milestone.Closed,
Deadline: milestone.Deadline,
}
}
func (o *milestone) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
milestone, resp, err := o.getClient().GetMilestone(owner, project, node.GetID().Int64())
if resp.StatusCode == 404 {
return false
}
if err != nil {
panic(fmt.Errorf("milestone %v %w", o, err))
}
o.forgejoMilestone = milestone
return true
}
func (o *milestone) Put(ctx context.Context) id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
milestone, _, err := o.getClient().CreateMilestone(owner, project, forgejo_sdk.CreateMilestoneOption{
Title: o.forgejoMilestone.Title,
Description: o.forgejoMilestone.Description,
State: o.forgejoMilestone.State,
Deadline: o.forgejoMilestone.Deadline,
})
if err != nil {
panic(err)
}
o.forgejoMilestone = milestone
return id.NewNodeID(o.GetNativeID())
}
func (o *milestone) Patch(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
_, _, err := o.getClient().EditMilestone(owner, project, node.GetID().Int64(), forgejo_sdk.EditMilestoneOption{
Title: o.forgejoMilestone.Title,
Description: &o.forgejoMilestone.Description,
State: &o.forgejoMilestone.State,
Deadline: o.forgejoMilestone.Deadline,
})
if err != nil {
panic(err)
}
}
func (o *milestone) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
_, err := o.getClient().DeleteMilestone(owner, project, node.GetID().Int64())
if err != nil {
panic(err)
}
}

View file

@ -0,0 +1,42 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type milestones struct {
container
}
func (o *milestones) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
var err error
var forgejoMilestones []*forgejo_sdk.Milestone
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
forgejoMilestones, _, err = o.getClient().ListRepoMilestones(owner, project, forgejo_sdk.ListMilestoneOption{
ListOptions: forgejo_sdk.ListOptions{Page: page, PageSize: pageSize},
})
if err != nil {
panic(fmt.Errorf("error while listing milestones: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoMilestones...)...)
}
func newMilestones() generic.NodeDriverInterface {
return &milestones{}
}

16
forges/forgejo/options.go Normal file
View file

@ -0,0 +1,16 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
forgejo_options "code.forgejo.org/f3/gof3/v3/forges/forgejo/options"
"code.forgejo.org/f3/gof3/v3/options"
)
func newOptions() options.Interface {
o := &forgejo_options.Options{}
o.SetName(forgejo_options.Name)
return o
}

View file

@ -0,0 +1,10 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package options
const (
Name = "forgejo"
NameAliasGitea = "gitea"
)

View file

@ -0,0 +1,33 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package options
import (
"context"
"code.forgejo.org/f3/gof3/v3/forges/helpers/auth"
"code.forgejo.org/f3/gof3/v3/options"
options_http "code.forgejo.org/f3/gof3/v3/options/http"
"code.forgejo.org/f3/gof3/v3/options/logger"
"github.com/urfave/cli/v3"
)
type Options struct {
options.Options
logger.OptionsLogger
auth.ForgeAuth
options_http.Implementation
Version string
}
func (o *Options) FromFlags(ctx context.Context, c *cli.Command, prefix string) {
o.ForgeAuth.FromFlags(ctx, c, prefix)
}
func (o *Options) GetFlags(prefix, category string) []cli.Flag {
return o.ForgeAuth.GetFlags(prefix, category)
}

View file

@ -0,0 +1,125 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type organization struct {
common
forgejoOrganization *forgejo_sdk.Organization
}
var _ f3_tree.ForgeDriverInterface = &organization{}
func newOrganization() generic.NodeDriverInterface {
return &organization{}
}
func (o *organization) SetNative(organization any) {
o.forgejoOrganization = organization.(*forgejo_sdk.Organization)
}
func (o *organization) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoOrganization.ID)
}
func (o *organization) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *organization) ToFormat() f3.Interface {
if o.forgejoOrganization == nil {
return o.NewFormat()
}
return &f3.Organization{
Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoOrganization.ID)),
Name: o.forgejoOrganization.UserName,
FullName: o.forgejoOrganization.FullName,
}
}
func (o *organization) FromFormat(content f3.Interface) {
organization := content.(*f3.Organization)
o.forgejoOrganization = &forgejo_sdk.Organization{
ID: util.ParseInt(organization.GetID()),
UserName: organization.Name,
FullName: organization.FullName,
}
}
func (o *organization) getName(ctx context.Context) string {
node := o.GetNode()
organizations := f3_tree.GetFirstNodeKind(node, f3_tree.KindOrganizations).GetDriver().(*organizations)
return organizations.getNameFromID(ctx, node.GetID().Int64())
}
func (o *organization) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
name := o.getName(ctx)
if name == "" {
return false
}
organization, resp, err := o.getClient().GetOrg(name)
if resp.StatusCode == 404 {
return false
}
if err != nil {
panic(fmt.Errorf("organization %v %w", o, err))
}
o.forgejoOrganization = organization
return true
}
func (o *organization) Patch(context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
_, err := o.getClient().EditOrg(o.forgejoOrganization.UserName, forgejo_sdk.EditOrgOption{
FullName: o.forgejoOrganization.FullName,
})
if err != nil {
panic(err)
}
}
func (o *organization) Put(context.Context) id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
organization, _, err := o.getClient().CreateOrg(forgejo_sdk.CreateOrgOption{
Name: o.forgejoOrganization.UserName,
FullName: o.forgejoOrganization.FullName,
})
if err != nil {
panic(err)
}
o.forgejoOrganization = organization
o.Trace("%s", organization)
return id.NewNodeID(o.GetNativeID())
}
func (o *organization) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
_, err := o.getClient().DeleteOrg(o.forgejoOrganization.UserName)
if err != nil {
panic(err)
}
}

View file

@ -0,0 +1,86 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"strings"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type organizations struct {
container
}
func (o *organizations) listOrganizationsPage(ctx context.Context, page int) []*forgejo_sdk.Organization {
pageSize := o.getPageSize()
var organizationFounds []*forgejo_sdk.Organization
var err error
organizationFounds, _, err = o.getClient().ListOrgs(forgejo_sdk.ListOrgsOptions{
ListOptions: forgejo_sdk.ListOptions{Page: page, PageSize: pageSize},
})
if err != nil {
panic(fmt.Errorf("error while listing organizations: %v", err))
}
return organizationFounds
}
func (o *organizations) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(o.listOrganizationsPage(ctx, page)...)...)
}
func (o *organizations) GetIDFromName(ctx context.Context, name string) id.NodeID {
organization, resp, err := o.getClient().GetOrg(name)
if resp.StatusCode == 404 {
return id.NilID
}
if err != nil {
if strings.Contains(err.Error(), "organization not found") {
return id.NilID
}
panic(fmt.Errorf("organization %v %w", o, err))
}
return id.NewNodeID(organization.ID)
}
func (o *organizations) getNameFromID(ctx context.Context, i int64) string {
o.Trace("%d", i)
node := o.GetNode()
nodeID := id.NewNodeID(i)
organization := node.GetChild(nodeID)
if organization != generic.NilNode {
return organization.ToFormat().(*f3.Organization).Name
}
o.Trace("look for %d", i)
for page := 1; ; page++ {
organizations := o.listOrganizationsPage(ctx, page)
if len(organizations) == 0 {
break
}
o.Trace("look for %d page %d", i, page)
for _, organization := range organizations {
o.Trace("look for %d page %d organization %v", i, page, organization)
if organization.ID == i {
return organization.UserName
}
}
}
o.Trace("no organization found for id %d", i)
return ""
}
func newOrganizations() generic.NodeDriverInterface {
return &organizations{}
}

189
forges/forgejo/project.go Normal file
View file

@ -0,0 +1,189 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"strings"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type project struct {
common
forgejoProject *forgejo_sdk.Repository
forked *f3.Reference
}
var _ f3_tree.ForgeDriverInterface = &project{}
func newProject() generic.NodeDriverInterface {
return &project{}
}
func (o *project) SetNative(project any) {
o.forgejoProject = project.(*forgejo_sdk.Repository)
}
func (o *project) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoProject.ID)
}
func (o *project) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *project) getForkedReference() *f3.Reference {
if !o.forgejoProject.Fork {
return nil
}
if o.forked == nil {
_, resp, err := o.getClient().GetOrg(o.forgejoProject.Parent.Owner.UserName)
owners := "organizations"
if resp.StatusCode == 404 {
owners = "users"
} else if err != nil {
panic(fmt.Errorf("get org %v %w", o, err))
}
o.forked = f3_tree.NewProjectReference(owners, fmt.Sprintf("%d", o.forgejoProject.Parent.Owner.ID), fmt.Sprintf("%d", o.forgejoProject.Parent.ID))
}
return o.forked
}
func (o *project) ToFormat() f3.Interface {
if o.forgejoProject == nil {
return o.NewFormat()
}
return &f3.Project{
Common: f3.NewCommon(o.GetNativeID()),
Name: o.forgejoProject.Name,
IsPrivate: o.forgejoProject.Private,
Description: o.forgejoProject.Description,
DefaultBranch: o.forgejoProject.DefaultBranch,
Forked: o.getForkedReference(),
}
}
func (o *project) FromFormat(content f3.Interface) {
project := content.(*f3.Project)
o.forgejoProject = &forgejo_sdk.Repository{
ID: util.ParseInt(project.GetID()),
Name: project.Name,
Private: project.IsPrivate,
Description: project.Description,
DefaultBranch: project.DefaultBranch,
}
o.forked = project.Forked
}
func (o *project) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
var project *forgejo_sdk.Repository
var err error
var resp *forgejo_sdk.Response
if node.GetID() != id.NilID {
project, resp, err = o.getClient().GetRepoByID(node.GetID().Int64())
} else {
panic("GetID() == 0")
}
if resp.StatusCode == 404 {
return false
}
if err != nil {
if strings.Contains(err.Error(), "project not found") {
return false
}
panic(fmt.Errorf("project %v %w", o, err))
}
o.forgejoProject = project
return true
}
func (o *project) Put(ctx context.Context) id.NodeID {
owner := f3_tree.GetOwner(o.GetNode()).ToFormat()
var ownerName string
var isOrganization bool
switch v := owner.(type) {
case *f3.User:
ownerName = v.UserName
o.maybeSudoName(ownerName)
case *f3.Organization:
ownerName = v.Name
isOrganization = true
default:
panic(fmt.Errorf("Put unexpected type %T", owner))
}
defer o.notSudo()
if o.forked == nil {
var repo *forgejo_sdk.Repository
var err error
if isOrganization {
repo, _, err = o.getClient().CreateOrgRepo(ownerName, forgejo_sdk.CreateRepoOption{
Name: o.forgejoProject.Name,
Description: o.forgejoProject.Description,
Private: o.forgejoProject.Private,
DefaultBranch: o.forgejoProject.DefaultBranch,
})
} else {
repo, _, err = o.getClient().CreateRepo(forgejo_sdk.CreateRepoOption{
Name: o.forgejoProject.Name,
Description: o.forgejoProject.Description,
Private: o.forgejoProject.Private,
DefaultBranch: o.forgejoProject.DefaultBranch,
})
}
if err != nil {
panic(err)
}
o.forgejoProject = repo
o.Trace("project created %d", o.forgejoProject.ID)
} else {
options := forgejo_sdk.CreateForkOption{
Name: &o.forgejoProject.Name,
}
owner, project := f3_tree.ResolveProjectReference(ctx, o.getTree(), o.forked)
repo, _, err := o.getClient().CreateFork(owner, project, options)
if err != nil {
panic(fmt.Errorf("CreateFork %s %s - %w", owner, project, err))
}
o.forgejoProject = repo
o.Trace("forked project created %d", o.forgejoProject.ID)
}
return id.NewNodeID(o.GetNativeID())
}
func (o *project) Patch(context.Context) {
owner := f3_tree.GetOwnerName(o.GetNode())
repo, _, err := o.getClient().EditRepo(owner, o.forgejoProject.Name, forgejo_sdk.EditRepoOption{
Description: &o.forgejoProject.Description,
})
if err != nil {
panic(err)
}
o.forgejoProject = repo
o.Trace("%d", o.forgejoProject.ID)
}
func (o *project) Delete(ctx context.Context) {
_, err := o.getClient().DeleteRepo(o.forgejoProject.Owner.UserName, o.forgejoProject.Name)
if err != nil {
panic(err)
}
}

View file

@ -0,0 +1,66 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type projects struct {
container
}
func (o *projects) GetIDFromName(ctx context.Context, name string) id.NodeID {
owner := f3_tree.GetOwnerName(o.GetNode())
forgejoProject, resp, err := o.getClient().GetRepo(owner, name)
if resp.StatusCode == 404 {
return id.NilID
}
if err != nil {
panic(fmt.Errorf("error GetRepo(%s, %s): %v", owner, name, err))
}
return id.NewNodeID(forgejoProject.ID)
}
func (o *projects) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
var err error
var forgejoProjects []*forgejo_sdk.Repository
owner := f3_tree.GetOwner(o.GetNode()).ToFormat()
switch v := owner.(type) {
case *f3.User:
forgejoProjects, _, err = o.getClient().ListUserRepos(v.UserName, forgejo_sdk.ListReposOptions{
ListOptions: forgejo_sdk.ListOptions{Page: page, PageSize: pageSize},
})
case *f3.Organization:
forgejoProjects, _, err = o.getClient().ListOrgRepos(v.Name, forgejo_sdk.ListOrgReposOptions{
ListOptions: forgejo_sdk.ListOptions{Page: page, PageSize: pageSize},
})
default:
panic(fmt.Errorf("unexpected type %T", owner))
}
if err != nil {
panic(fmt.Errorf("error while listing projects: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoProjects...)...)
}
func newProjects() generic.NodeDriverInterface {
return &projects{}
}

View file

@ -0,0 +1,289 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"time"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/path"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type pullRequest struct {
common
forgejoPullRequest *forgejo_sdk.PullRequest
headRepository *f3.Reference
baseRepository *f3.Reference
fetchFunc f3.PullRequestFetchFunc
}
var _ f3_tree.ForgeDriverInterface = &pullRequest{}
func newPullRequest() generic.NodeDriverInterface {
return &pullRequest{}
}
func (o *pullRequest) SetNative(pullRequest any) {
o.forgejoPullRequest = pullRequest.(*forgejo_sdk.PullRequest)
}
func (o *pullRequest) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoPullRequest.Index)
}
func (o *pullRequest) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *pullRequest) repositoryToReference(ctx context.Context, repository *forgejo_sdk.Repository) *f3.Reference {
if repository == nil {
panic("unexpected nil repository")
}
owners := o.getForge().getOwnersPath(ctx, fmt.Sprintf("%d", repository.Owner.ID))
return f3_tree.NewRepositoryReference(owners.String(), repository.Owner.ID, repository.ID)
}
func (o *pullRequest) referenceToRepository(reference *f3.Reference) *forgejo_sdk.Repository {
var owner, project int64
if reference.Get() == "../../repository/vcs" {
project = f3_tree.GetProjectID(o.GetNode())
owner = f3_tree.GetOwnerID(o.GetNode())
} else {
p := f3_tree.ToPath(path.PathAbsolute(generic.NewElementNode, o.GetNode().GetCurrentPath().String(), reference.Get()))
o.Trace("%v %v", o.GetNode().GetCurrentPath().ReadableString(), p)
owner, project = p.OwnerAndProjectID()
}
return &forgejo_sdk.Repository{
ID: project,
Owner: &forgejo_sdk.User{
ID: owner,
},
}
}
func (o *pullRequest) ToFormat() f3.Interface {
if o.forgejoPullRequest == nil {
return o.NewFormat()
}
var milestone *f3.Reference
if o.forgejoPullRequest.Milestone != nil {
milestone = f3_tree.NewIssueMilestoneReference(o.forgejoPullRequest.Milestone.ID)
}
createdAt := time.Time{}
if o.forgejoPullRequest.Created != nil {
createdAt = *o.forgejoPullRequest.Created
}
updatedAt := time.Time{}
if o.forgejoPullRequest.Created != nil {
updatedAt = *o.forgejoPullRequest.Updated
}
var (
headRef string
headSHA string
)
if o.forgejoPullRequest.Head != nil {
headSHA = o.forgejoPullRequest.Head.Sha
headRef = o.forgejoPullRequest.Head.Ref
}
var (
baseRef string
baseSHA string
)
if o.forgejoPullRequest.Base != nil {
baseSHA = o.forgejoPullRequest.Base.Sha
baseRef = o.forgejoPullRequest.Base.Ref
}
var mergeCommitSHA string
if o.forgejoPullRequest.MergedCommitID != nil {
mergeCommitSHA = *o.forgejoPullRequest.MergedCommitID
}
closedAt := o.forgejoPullRequest.Closed
if o.forgejoPullRequest.Merged != nil && closedAt == nil {
closedAt = o.forgejoPullRequest.Merged
}
return &f3.PullRequest{
Title: o.forgejoPullRequest.Title,
Common: f3.NewCommon(o.GetNativeID()),
PosterID: f3_tree.NewUserReference(o.forgejoPullRequest.Poster.ID),
Content: o.forgejoPullRequest.Body,
State: string(o.forgejoPullRequest.State),
Created: createdAt,
Updated: updatedAt,
Closed: closedAt,
Milestone: milestone,
Merged: o.forgejoPullRequest.HasMerged,
MergedTime: o.forgejoPullRequest.Merged,
MergeCommitSHA: mergeCommitSHA,
IsLocked: o.forgejoPullRequest.IsLocked,
Head: f3.PullRequestBranch{
Ref: headRef,
SHA: headSHA,
Repository: o.headRepository,
},
Base: f3.PullRequestBranch{
Ref: baseRef,
SHA: baseSHA,
Repository: o.baseRepository,
},
FetchFunc: o.fetchFunc,
}
}
func (o *pullRequest) FromFormat(content f3.Interface) {
pullRequest := content.(*f3.PullRequest)
var milestone *forgejo_sdk.Milestone
if pullRequest.Milestone != nil {
milestone = &forgejo_sdk.Milestone{
ID: pullRequest.Milestone.GetIDAsInt(),
}
}
o.forgejoPullRequest = &forgejo_sdk.PullRequest{
Index: util.ParseInt(pullRequest.GetID()),
Poster: &forgejo_sdk.User{
ID: pullRequest.PosterID.GetIDAsInt(),
},
Title: pullRequest.Title,
Body: pullRequest.Content,
Milestone: milestone,
State: forgejo_sdk.StateType(pullRequest.State),
IsLocked: pullRequest.IsLocked,
HasMerged: pullRequest.Merged,
Merged: pullRequest.MergedTime,
MergedCommitID: &pullRequest.MergeCommitSHA,
Base: &forgejo_sdk.PRBranchInfo{
Ref: pullRequest.Base.Ref,
Sha: pullRequest.Base.SHA,
Repository: o.referenceToRepository(pullRequest.Base.Repository),
},
Head: &forgejo_sdk.PRBranchInfo{
Ref: pullRequest.Head.Ref,
Sha: pullRequest.Head.SHA,
Repository: o.referenceToRepository(pullRequest.Head.Repository),
},
Created: &pullRequest.Created,
Updated: &pullRequest.Updated,
Closed: pullRequest.Closed,
}
if o.forgejoPullRequest.Base.Repository.ID != o.forgejoPullRequest.Head.Repository.ID {
o.forgejoPullRequest.Head.Repository.Fork = true
}
o.fetchFunc = pullRequest.FetchFunc
}
func (o *pullRequest) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
ownerName := f3_tree.GetOwnerName(o.GetNode())
projectName := f3_tree.GetProjectName(o.GetNode())
pr, resp, err := o.getClient().GetPullRequest(ownerName, projectName, node.GetID().Int64())
if resp.StatusCode == 404 {
return false
}
if err != nil {
panic(fmt.Errorf("pullRequest %v %w", o, err))
}
o.headRepository = o.repositoryToReference(ctx, o.forgejoPullRequest.Head.Repository)
o.baseRepository = o.repositoryToReference(ctx, o.forgejoPullRequest.Base.Repository)
o.forgejoPullRequest = pr
return true
}
func (o *pullRequest) GetPullRequestHead(ctx context.Context) string {
head := o.forgejoPullRequest.Head
if head.Repository.Fork {
return o.getForge().getOwnersName(ctx, fmt.Sprintf("%d", head.Repository.Owner.ID)) + ":" + head.Ref
}
return head.Ref
}
func (o *pullRequest) GetPullRequestPushRefs() []string {
return []string{
fmt.Sprintf("refs/f3/%s/head", o.GetNativeID()),
fmt.Sprintf("refs/pull/%s/head", o.GetNativeID()),
}
}
func (o *pullRequest) GetPullRequestRef() string {
return fmt.Sprintf("refs/pull/%s/head", o.GetNativeID())
}
func (o *pullRequest) Put(ctx context.Context) id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(node)
project := f3_tree.GetProjectName(node)
o.maybeSudoID(ctx, o.forgejoPullRequest.Poster.ID)
defer o.notSudo()
options := forgejo_sdk.CreatePullRequestOption{
Head: o.GetPullRequestHead(ctx),
Base: o.forgejoPullRequest.Base.Ref,
Title: o.forgejoPullRequest.Title,
Body: o.forgejoPullRequest.Body,
}
pr, _, err := o.getClient().CreatePullRequest(owner, project, options)
if err != nil {
panic(fmt.Errorf("%+v: %w", options, err))
}
o.forgejoPullRequest = pr
return id.NewNodeID(o.GetNativeID())
}
func (o *pullRequest) Patch(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
o.maybeSudoID(ctx, o.forgejoPullRequest.Poster.ID)
defer o.notSudo()
_, _, err := o.getClient().EditPullRequest(owner, project, node.GetID().Int64(), forgejo_sdk.EditPullRequestOption{
Title: o.forgejoPullRequest.Title,
Body: o.forgejoPullRequest.Body,
})
if err != nil {
panic(err)
}
}
func (o *pullRequest) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
_, err := o.getClient().DeleteIssue(owner, project, node.GetID().Int64())
if err != nil {
panic(err)
}
}

View file

@ -0,0 +1,47 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type pullRequests struct {
container
}
func (o *pullRequests) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
var err error
var forgejoPullRequests []*forgejo_sdk.PullRequest
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
forgejoPullRequests, resp, err := o.getClient().ListRepoPullRequests(owner, project, forgejo_sdk.ListPullRequestsOptions{
ListOptions: forgejo_sdk.ListOptions{Page: page, PageSize: pageSize},
State: forgejo_sdk.StateAll,
})
// When there are not pull requests it returns 404 instead of an empty list
if resp.StatusCode == 404 {
return generic.NewChildrenSlice(0)
}
if err != nil {
panic(fmt.Errorf("error while listing pullRequests: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoPullRequests...)...)
}
func newPullRequests() generic.NodeDriverInterface {
return &pullRequests{}
}

130
forges/forgejo/reaction.go Normal file
View file

@ -0,0 +1,130 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type reaction struct {
common
forgejoReaction *forgejo_sdk.Reaction
exists bool
id string
}
var _ f3_tree.ForgeDriverInterface = &pullRequest{}
func newReaction() generic.NodeDriverInterface {
return &reaction{}
}
func (o *reaction) SetNative(reaction any) {
o.forgejoReaction = reaction.(*forgejo_sdk.Reaction)
o.exists = true
o.id = fmt.Sprintf("%d %s", o.forgejoReaction.User.ID, o.forgejoReaction.Reaction)
}
func (o *reaction) GetNativeID() string {
return o.id
}
func (o *reaction) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *reaction) ToFormat() f3.Interface {
if o.forgejoReaction == nil {
return o.NewFormat()
}
return &f3.Reaction{
Common: f3.NewCommon(o.GetNativeID()),
UserID: f3_tree.NewUserReference(o.forgejoReaction.User.ID),
Content: o.forgejoReaction.Reaction,
}
}
func (o *reaction) FromFormat(content f3.Interface) {
reaction := content.(*f3.Reaction)
o.forgejoReaction = &forgejo_sdk.Reaction{
User: &forgejo_sdk.User{
ID: reaction.UserID.GetIDAsInt(),
},
Reaction: reaction.Content,
}
o.id = reaction.GetID()
}
func (o *reaction) Get(ctx context.Context) bool {
return o.exists
}
func (o *reaction) Put(ctx context.Context) id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
reactionable := f3_tree.GetReactionable(o.GetNode())
o.maybeSudoID(ctx, o.forgejoReaction.User.ID)
defer o.notSudo()
var err error
var reaction *forgejo_sdk.Reaction
switch reactionable.GetKind() {
case f3_tree.KindIssue, f3_tree.KindPullRequest:
commentable := f3_tree.GetCommentableID(o.GetNode())
reaction, _, err = o.getClient().PostIssueReaction(owner, project, commentable, o.forgejoReaction.Reaction)
case f3_tree.KindComment:
comment := f3_tree.GetCommentID(o.GetNode())
reaction, _, err = o.getClient().PostIssueCommentReaction(owner, project, comment, o.forgejoReaction.Reaction)
default:
panic(fmt.Errorf("unexpected type %T", owner))
}
if err != nil {
panic(err)
}
o.SetNative(reaction)
return id.NewNodeID(o.GetNativeID())
}
func (o *reaction) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
reactionable := f3_tree.GetReactionable(o.GetNode())
reactionableID := f3_tree.GetReactionableID(o.GetNode())
var err error
switch reactionable.GetKind() {
case f3_tree.KindIssue, f3_tree.KindPullRequest:
_, err = o.getClient().DeleteIssueReaction(owner, project, reactionableID, o.forgejoReaction.Reaction)
case f3_tree.KindComment:
_, err = o.getClient().DeleteIssueCommentReaction(owner, project, reactionableID, o.forgejoReaction.Reaction)
default:
panic(fmt.Errorf("unexpected type %T", owner))
}
if err != nil {
panic(err)
}
o.exists = false
}

View file

@ -0,0 +1,48 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type reactions struct {
container
}
func (o *reactions) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
var err error
var forgejoReactions []*forgejo_sdk.Reaction
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
reactionable := f3_tree.GetReactionable(o.GetNode())
reactionableID := f3_tree.GetReactionableID(o.GetNode())
switch reactionable.GetKind() {
case f3_tree.KindIssue, f3_tree.KindPullRequest:
forgejoReactions, _, err = o.getClient().GetIssueReactions(owner, project, reactionableID)
case f3_tree.KindComment:
forgejoReactions, _, err = o.getClient().GetIssueCommentReactions(owner, project, reactionableID)
default:
panic(fmt.Errorf("unexpected type %T", owner))
}
if err != nil {
panic(fmt.Errorf("error while listing reactions: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoReactions...)...)
}
func newReactions() generic.NodeDriverInterface {
return &reactions{}
}

154
forges/forgejo/release.go Normal file
View file

@ -0,0 +1,154 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type release struct {
common
forgejoRelease *forgejo_sdk.Release
}
var _ f3_tree.ForgeDriverInterface = &pullRequest{}
func newRelease() generic.NodeDriverInterface {
return &release{}
}
func (o *release) SetNative(release any) {
o.forgejoRelease = release.(*forgejo_sdk.Release)
}
func (o *release) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoRelease.ID)
}
func (o *release) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *release) ToFormat() f3.Interface {
if o.forgejoRelease == nil {
return o.NewFormat()
}
return &f3.Release{
Common: f3.NewCommon(o.GetNativeID()),
TagName: o.forgejoRelease.TagName,
TargetCommitish: o.forgejoRelease.Target,
Name: o.forgejoRelease.Title,
Body: o.forgejoRelease.Note,
Draft: o.forgejoRelease.IsDraft,
Prerelease: o.forgejoRelease.IsPrerelease,
PublisherID: f3_tree.NewUserReference(o.forgejoRelease.Publisher.ID),
Created: o.forgejoRelease.CreatedAt,
}
}
func (o *release) FromFormat(content f3.Interface) {
release := content.(*f3.Release)
o.forgejoRelease = &forgejo_sdk.Release{
ID: util.ParseInt(release.GetID()),
TagName: release.TagName,
Target: release.TargetCommitish,
Title: release.Name,
Note: release.Body,
IsDraft: release.Draft,
IsPrerelease: release.Prerelease,
Publisher: &forgejo_sdk.User{
ID: release.PublisherID.GetIDAsInt(),
},
CreatedAt: release.Created,
}
}
func (o *release) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
release, resp, err := o.getClient().GetRelease(owner, project, node.GetID().Int64())
if resp.StatusCode == 404 {
return false
}
if err != nil {
panic(fmt.Errorf("release %v %w", o, err))
}
o.forgejoRelease = release
return true
}
func (o *release) Put(ctx context.Context) id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
o.maybeSudoID(ctx, o.forgejoRelease.Publisher.ID)
defer o.notSudo()
release, _, err := o.getClient().CreateRelease(owner, project, forgejo_sdk.CreateReleaseOption{
TagName: o.forgejoRelease.TagName,
Target: o.forgejoRelease.Target,
Title: o.forgejoRelease.Title,
Note: o.forgejoRelease.Note,
IsDraft: o.forgejoRelease.IsDraft,
IsPrerelease: o.forgejoRelease.IsPrerelease,
})
if err != nil {
panic(err)
}
o.forgejoRelease = release
return id.NewNodeID(o.GetNativeID())
}
func (o *release) Patch(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
_, _, err := o.getClient().EditRelease(owner, project, node.GetID().Int64(), forgejo_sdk.EditReleaseOption{
TagName: o.forgejoRelease.TagName,
Target: o.forgejoRelease.Target,
Title: o.forgejoRelease.Title,
Note: o.forgejoRelease.Note,
IsDraft: &o.forgejoRelease.IsDraft,
IsPrerelease: &o.forgejoRelease.IsPrerelease,
})
if err != nil {
panic(err)
}
}
func (o *release) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
_, err := o.getClient().DeleteRelease(owner, project, node.GetID().Int64())
if err != nil {
panic(err)
}
}

View file

@ -0,0 +1,44 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type releases struct {
container
}
func (o *releases) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
var err error
var forgejoReleases []*forgejo_sdk.Release
if o.getProject().forgejoProject.HasReleases {
owner := f3_tree.GetOwnerName(o.GetNode())
projectName := f3_tree.GetProjectName(o.GetNode())
forgejoReleases, _, err = o.getClient().ListReleases(owner, projectName, forgejo_sdk.ListReleasesOptions{
ListOptions: forgejo_sdk.ListOptions{Page: page, PageSize: pageSize},
})
if err != nil {
panic(fmt.Errorf("error while listing releases: %v", err))
}
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoReleases...)...)
}
func newReleases() generic.NodeDriverInterface {
return &releases{}
}

View file

@ -0,0 +1,51 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type repositories struct {
container
}
func (o *repositories) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
children := generic.NewChildrenSlice(0)
if page > 1 {
return children
}
names := []string{f3.RepositoryNameDefault}
project := f3_tree.GetProject(o.GetNode()).ToFormat().(*f3.Project)
if project.HasWiki {
names = append(names, f3.RepositoryNameWiki)
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(names...)...)
}
func (o *repositories) GetIDFromName(ctx context.Context, name string) id.NodeID {
switch name {
case f3.RepositoryNameDefault:
case f3.RepositoryNameWiki:
project := f3_tree.GetProject(o.GetNode()).ToFormat().(*f3.Project)
if !project.HasWiki {
return id.NilID
}
default:
return id.NilID
}
return id.NewNodeID(name)
}
func newRepositories() generic.NodeDriverInterface {
return &repositories{}
}

View file

@ -0,0 +1,103 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
helpers_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/repository"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type repository struct {
common
name string
h helpers_repository.Interface
f *f3.Repository
}
func (o *repository) SetNative(repository any) {
o.name = repository.(string)
}
func (o *repository) GetNativeID() string {
return o.name
}
func (o *repository) NewFormat() f3.Interface {
return &f3.Repository{}
}
func (o *repository) ToFormat() f3.Interface {
return &f3.Repository{
Common: f3.NewCommon(o.GetNativeID()),
Name: o.GetNativeID(),
FetchFunc: o.f.FetchFunc,
}
}
func (o *repository) FromFormat(content f3.Interface) {
f := content.Clone().(*f3.Repository)
o.f = f
o.f.SetID(f.Name)
o.name = f.Name
}
func (o *repository) Get(ctx context.Context) bool {
return o.h.Get(ctx)
}
func (o *repository) Put(ctx context.Context) id.NodeID {
return o.upsert(ctx)
}
func (o *repository) Patch(ctx context.Context) {
o.upsert(ctx)
}
func (o *repository) upsert(ctx context.Context) id.NodeID {
o.Trace("%s", o.GetNativeID())
o.h.Upsert(ctx, o.f)
return id.NewNodeID(o.f.Name)
}
func (o *repository) urlToRepositoryURL(url string) string {
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
repositoryURL := fmt.Sprintf("%s/%s/%s", url, owner, project)
if o.f.GetID() == f3.RepositoryNameWiki {
repositoryURL += ".wiki"
}
return repositoryURL
}
func (o *repository) SetFetchFunc(fetchFunc func(ctx context.Context, destination string, internalRefs []string)) {
o.f.FetchFunc = fetchFunc
}
func (o *repository) GetRepositoryURL() string {
return o.urlToRepositoryURL(o.getURL())
}
func (o *repository) GetRepositoryPushURL() string {
return o.urlToRepositoryURL(o.getPushURL())
}
func (o *repository) GetRepositoryInternalRefs() []string {
return []string{"refs/pull/*"}
}
func newRepository(ctx context.Context) generic.NodeDriverInterface {
r := &repository{
f: &f3.Repository{},
}
r.h = helpers_repository.NewHelper(r)
return r
}

165
forges/forgejo/review.go Normal file
View file

@ -0,0 +1,165 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
helper_pullrequest "code.forgejo.org/f3/gof3/v3/forges/helpers/pullrequest"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type review struct {
common
forgejoReview *forgejo_sdk.PullReview
h helper_pullrequest.Interface
}
var _ f3_tree.ForgeDriverInterface = &review{}
func newReview() generic.NodeDriverInterface {
return &review{}
}
func (o *review) SetNative(review any) {
o.forgejoReview = review.(*forgejo_sdk.PullReview)
}
func (o *review) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoReview.ID)
}
func (o *review) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
var sdkStateToFormatState = map[string]string{
string(forgejo_sdk.ReviewStateApproved): f3.ReviewStateApproved,
string(forgejo_sdk.ReviewStatePending): f3.ReviewStatePending,
string(forgejo_sdk.ReviewStateComment): f3.ReviewStateCommented,
string(forgejo_sdk.ReviewStateRequestChanges): f3.ReviewStateChangesRequested,
string(forgejo_sdk.ReviewStateRequestReview): f3.ReviewStateRequestReview,
string(forgejo_sdk.ReviewStateUnknown): f3.ReviewStateUnknown,
}
var formatStateToSdkState = func() map[string]string {
r := make(map[string]string, len(sdkStateToFormatState))
for k, v := range sdkStateToFormatState {
r[v] = k
}
return r
}()
func convertState(m map[string]string, unknown, fromState string) string {
if toState, ok := m[fromState]; ok {
return toState
}
return unknown
}
func (o *review) ToFormat() f3.Interface {
if o.forgejoReview == nil {
return o.NewFormat()
}
review := &f3.Review{
Common: f3.NewCommon(o.GetNativeID()),
Official: o.forgejoReview.Official,
CommitID: o.forgejoReview.CommitID,
Content: o.forgejoReview.Body,
CreatedAt: o.forgejoReview.Submitted,
State: convertState(sdkStateToFormatState, f3.ReviewStateUnknown, string(o.forgejoReview.State)),
}
if o.forgejoReview.Reviewer != nil {
review.ReviewerID = f3_tree.NewUserReference(o.forgejoReview.Reviewer.ID)
}
return review
}
func (o *review) FromFormat(content f3.Interface) {
review := content.(*f3.Review)
o.forgejoReview = &forgejo_sdk.PullReview{
ID: util.ParseInt(review.GetID()),
Reviewer: &forgejo_sdk.User{
ID: review.ReviewerID.GetIDAsInt(),
},
Official: review.Official,
CommitID: review.CommitID,
Body: review.Content,
Submitted: review.CreatedAt,
State: forgejo_sdk.ReviewStateType(convertState(formatStateToSdkState, string(forgejo_sdk.ReviewStateUnknown), review.State)),
}
}
func (o *review) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
pullRequest := f3_tree.GetPullRequest(o.GetNode())
review, resp, err := o.getClient().GetPullReview(owner, project, pullRequest.GetID().Int64(), node.GetID().Int64())
if resp.StatusCode == 404 {
return false
}
if err != nil {
panic(fmt.Errorf("review %v %w", o, err))
}
o.forgejoReview = review
return true
}
func (o *review) Put(ctx context.Context) id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
pullRequest := f3_tree.GetPullRequest(o.GetNode())
o.maybeSudoID(ctx, o.forgejoReview.Reviewer.ID)
defer o.notSudo()
review, _, err := o.getClient().CreatePullReview(owner, project, pullRequest.GetID().Int64(), forgejo_sdk.CreatePullReviewOptions{
State: o.forgejoReview.State,
Body: o.forgejoReview.Body,
CommitID: o.forgejoReview.CommitID,
})
if err != nil {
panic(err)
}
o.forgejoReview = review
return id.NewNodeID(o.GetNativeID())
}
func (o *review) Patch(ctx context.Context) {
panic("cannot edit an existing review")
}
func (o *review) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
pullRequest := f3_tree.GetPullRequest(o.GetNode())
_, err := o.getClient().DeletePullReview(owner, project, pullRequest.GetID().Int64(), node.GetID().Int64())
if err != nil {
panic(err)
}
}

View file

@ -0,0 +1,168 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type reviewComment struct {
common
forgejoReviewComment *forgejo_sdk.PullReviewComment
}
var _ f3_tree.ForgeDriverInterface = &pullRequest{}
func newReviewComment() generic.NodeDriverInterface {
return &reviewComment{}
}
func (o *reviewComment) SetNative(reviewComment any) {
o.forgejoReviewComment = reviewComment.(*forgejo_sdk.PullReviewComment)
}
func (o *reviewComment) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoReviewComment.ID)
}
func (o *reviewComment) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *reviewComment) ToFormat() f3.Interface {
if o.forgejoReviewComment == nil {
return o.NewFormat()
}
line := int(o.forgejoReviewComment.LineNum)
if o.forgejoReviewComment.OldLineNum > 0 {
line = int(o.forgejoReviewComment.OldLineNum) * -1
}
return &f3.ReviewComment{
Common: f3.NewCommon(o.GetNativeID()),
PosterID: f3_tree.NewUserReference(o.forgejoReviewComment.Reviewer.ID),
Content: o.forgejoReviewComment.Body,
TreePath: o.forgejoReviewComment.Path,
DiffHunk: o.forgejoReviewComment.DiffHunk,
Line: line,
CommitID: o.forgejoReviewComment.CommitID,
CreatedAt: o.forgejoReviewComment.Created,
UpdatedAt: o.forgejoReviewComment.Updated,
}
}
func (o *reviewComment) FromFormat(content f3.Interface) {
reviewComment := content.(*f3.ReviewComment)
o.forgejoReviewComment = &forgejo_sdk.PullReviewComment{
ID: util.ParseInt(reviewComment.GetID()),
Reviewer: &forgejo_sdk.User{
ID: reviewComment.PosterID.GetIDAsInt(),
},
Path: reviewComment.TreePath,
Body: reviewComment.Content,
DiffHunk: reviewComment.DiffHunk,
CommitID: reviewComment.CommitID,
Created: reviewComment.CreatedAt,
Updated: reviewComment.UpdatedAt,
}
if reviewComment.Line > 0 {
o.forgejoReviewComment.LineNum = uint64(reviewComment.Line)
} else {
o.forgejoReviewComment.OldLineNum = uint64(reviewComment.Line * -1)
}
}
func (o *reviewComment) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
pullRequest := f3_tree.GetPullRequestID(o.GetNode())
review := f3_tree.GetReviewID(o.GetNode())
comment := f3_tree.GetReviewCommentID(o.GetNode())
reviewComment, resp, err := o.getClient().GetPullReviewComment(owner, project, pullRequest, review, comment)
if resp.StatusCode == 404 {
return false
}
if err != nil {
panic(fmt.Errorf("reviewComment %v %w", o, err))
}
o.forgejoReviewComment = reviewComment
return true
}
func (o *reviewComment) Put(ctx context.Context) id.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
pullRequest := f3_tree.GetPullRequestID(o.GetNode())
review := f3_tree.GetReviewID(o.GetNode())
o.maybeSudoID(ctx, o.forgejoReviewComment.Reviewer.ID)
defer o.notSudo()
reviewComment, _, err := o.getClient().CreatePullReviewComment(owner, project, pullRequest, review, forgejo_sdk.CreatePullReviewComment{
Path: o.forgejoReviewComment.Path,
Body: o.forgejoReviewComment.Body,
LineNum: o.forgejoReviewComment.LineNum,
OldLineNum: o.forgejoReviewComment.OldLineNum,
})
if err != nil {
panic(err)
}
o.forgejoReviewComment = reviewComment
return id.NewNodeID(o.GetNativeID())
}
func (o *reviewComment) Patch(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
_, _, err := o.getClient().EditIssueComment(owner, project, node.GetID().Int64(), forgejo_sdk.EditIssueCommentOption{
Body: o.forgejoReviewComment.Body,
})
if err != nil {
panic(err)
}
}
func (o *reviewComment) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
pullRequest := f3_tree.GetPullRequestID(o.GetNode())
review := f3_tree.GetReviewID(o.GetNode())
comment := f3_tree.GetReviewCommentID(o.GetNode())
resp, err := o.getClient().DeletePullReviewComment(owner, project, pullRequest, review, comment)
if resp.StatusCode != 204 {
panic(fmt.Errorf("unexpected status code deleting %d %d %v", comment, resp.StatusCode, resp))
}
if err != nil {
panic(err)
}
}

View file

@ -0,0 +1,39 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type reviewComments struct {
container
}
func (o *reviewComments) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
if page > 1 {
return generic.NewChildrenSlice(0)
}
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
pullRequest := f3_tree.GetPullRequestID(o.GetNode())
review := f3_tree.GetReviewID(o.GetNode())
reviewComments, _, err := o.getClient().ListPullReviewComments(owner, project, pullRequest, review)
if err != nil {
panic(fmt.Errorf("error while listing reviewComments: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(reviewComments...)...)
}
func newReviewComments() generic.NodeDriverInterface {
return &reviewComments{}
}

40
forges/forgejo/reviews.go Normal file
View file

@ -0,0 +1,40 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"fmt"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)
type reviews struct {
container
}
func (o *reviews) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
pullRequest := f3_tree.GetPullRequest(o.GetNode())
reviews, _, err := o.getClient().ListPullReviews(owner, project, pullRequest.GetID().Int64(), forgejo_sdk.ListPullReviewsOptions{
ListOptions: forgejo_sdk.ListOptions{Page: page, PageSize: pageSize},
})
if err != nil {
panic(fmt.Errorf("error while listing reviews: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(reviews...)...)
}
func newReviews() generic.NodeDriverInterface {
return &reviews{}
}

42
forges/forgejo/root.go Normal file
View file

@ -0,0 +1,42 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type root struct {
generic.NullDriver
content f3.Interface
}
func newRoot(content f3.Interface) generic.NodeDriverInterface {
return &root{
content: content,
}
}
func (o *root) FromFormat(content f3.Interface) {
o.content = content
}
func (o *root) ToFormat() f3.Interface {
return o.content
}
func (o *root) Get(context.Context) bool { return true }
func (o *root) Put(context.Context) id.NodeID {
return id.NilID
}
func (o *root) Patch(context.Context) {
}

View file

@ -0,0 +1,47 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"time"
)
// CronTask represents a Cron task
type CronTask struct {
Name string `json:"name"`
Schedule string `json:"schedule"`
Next time.Time `json:"next"`
Prev time.Time `json:"prev"`
ExecTimes int64 `json:"exec_times"`
}
// ListCronTaskOptions list options for ListCronTasks
type ListCronTaskOptions struct {
ListOptions
}
// ListCronTasks list available cron tasks
func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
return nil, nil, err
}
opt.setDefaults()
ct := make([]*CronTask, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/cron?%s", opt.getURLQuery().Encode()), jsonHeader, nil, &ct)
return ct, resp, err
}
// RunCronTasks run a cron task
func (c *Client) RunCronTasks(task string) (*Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
return nil, err
}
if err := escapeValidatePathSegments(&task); err != nil {
return nil, err
}
_, resp, err := c.getResponse("POST", fmt.Sprintf("/admin/cron/%s", task), jsonHeader, nil)
return resp, err
}

View file

@ -0,0 +1,39 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// AdminListOrgsOptions options for listing admin's organizations
type AdminListOrgsOptions struct {
ListOptions
}
// AdminListOrgs lists all orgs
func (c *Client) AdminListOrgs(opt AdminListOrgsOptions) ([]*Organization, *Response, error) {
opt.setDefaults()
orgs := make([]*Organization, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs)
return orgs, resp, err
}
// AdminCreateOrg create an organization
func (c *Client) AdminCreateOrg(user string, opt CreateOrgOption) (*Organization, *Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
org := new(Organization)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/orgs", user), jsonHeader, bytes.NewReader(body), org)
return org, resp, err
}

View file

@ -0,0 +1,25 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// AdminCreateRepo create a repo
func (c *Client) AdminCreateRepo(user string, opt CreateRepoOption) (*Repository, *Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/repos", user), jsonHeader, bytes.NewReader(body), repo)
return repo, resp, err
}

View file

@ -0,0 +1,130 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// AdminListUsersOptions options for listing admin users
type AdminListUsersOptions struct {
ListOptions
}
// AdminListUsers lists all users
func (c *Client) AdminListUsers(opt AdminListUsersOptions) ([]*User, *Response, error) {
opt.setDefaults()
users := make([]*User, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/users?%s", opt.getURLQuery().Encode()), nil, nil, &users)
return users, resp, err
}
// CreateUserOption create user options
type CreateUserOption struct {
SourceID int64 `json:"source_id"`
LoginName string `json:"login_name"`
Username string `json:"username"`
FullName string `json:"full_name"`
Email string `json:"email"`
Password string `json:"password"`
MustChangePassword *bool `json:"must_change_password"`
SendNotify bool `json:"send_notify"`
Visibility *VisibleType `json:"visibility"`
}
// Validate the CreateUserOption struct
func (opt CreateUserOption) Validate() error {
if len(opt.Email) == 0 {
return fmt.Errorf("email is empty")
}
if len(opt.Username) == 0 {
return fmt.Errorf("username is empty")
}
return nil
}
// AdminCreateUser create a user
func (c *Client) AdminCreateUser(opt CreateUserOption) (*User, *Response, error) {
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
user := new(User)
resp, err := c.getParsedResponse("POST", "/admin/users", jsonHeader, bytes.NewReader(body), user)
return user, resp, err
}
// EditUserOption edit user options
type EditUserOption struct {
SourceID int64 `json:"source_id"`
LoginName string `json:"login_name"`
Email *string `json:"email"`
FullName *string `json:"full_name"`
Password string `json:"password"`
Description *string `json:"description"`
MustChangePassword *bool `json:"must_change_password"`
Website *string `json:"website"`
Location *string `json:"location"`
Active *bool `json:"active"`
Admin *bool `json:"admin"`
AllowGitHook *bool `json:"allow_git_hook"`
AllowImportLocal *bool `json:"allow_import_local"`
MaxRepoCreation *int `json:"max_repo_creation"`
ProhibitLogin *bool `json:"prohibit_login"`
AllowCreateOrganization *bool `json:"allow_create_organization"`
Restricted *bool `json:"restricted"`
Visibility *VisibleType `json:"visibility"`
}
// AdminEditUser modify user informations
func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/admin/users/%s", user), jsonHeader, bytes.NewReader(body))
return resp, err
}
// AdminDeleteUser delete one user according name
func (c *Client) AdminDeleteUser(user string) (*Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s", user), nil, nil)
return resp, err
}
// AdminCreateUserPublicKey adds a public key for the user
func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*PublicKey, *Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
key := new(PublicKey)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/keys", user), jsonHeader, bytes.NewReader(body), key)
return key, resp, err
}
// AdminDeleteUserPublicKey deletes a user's public key
func (c *Client) AdminDeleteUserPublicKey(user string, keyID int) (*Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s/keys/%d", user, keyID), nil, nil)
return resp, err
}

View file

@ -0,0 +1,38 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
//go:build !windows
package sdk
import (
"fmt"
"net"
"os"
"golang.org/x/crypto/ssh/agent"
)
// hasAgent returns true if the ssh agent is available
func hasAgent() bool {
if _, err := os.Stat(os.Getenv("SSH_AUTH_SOCK")); err != nil {
return false
}
return true
}
// GetAgent returns a ssh agent
func GetAgent() (agent.Agent, error) {
if !hasAgent() {
return nil, fmt.Errorf("no ssh agent available")
}
sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
if err != nil {
return nil, err
}
return agent.NewClient(sshAgent), nil
}

View file

@ -0,0 +1,28 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
//go:build windows
package sdk
import (
"fmt"
"github.com/davidmz/go-pageant"
"golang.org/x/crypto/ssh/agent"
)
// hasAgent returns true if pageant is available
func hasAgent() bool {
return pageant.Available()
}
// GetAgent returns a ssh agent
func GetAgent() (agent.Agent, error) {
if !hasAgent() {
return nil, fmt.Errorf("no pageant available")
}
return pageant.New(), nil
}

View file

@ -0,0 +1,112 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"time"
)
// Attachment a generic attachment
type Attachment struct {
ID int64 `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
DownloadCount int64 `json:"download_count"`
Created time.Time `json:"created_at"`
UUID string `json:"uuid"`
DownloadURL string `json:"browser_download_url"`
}
// ListReleaseAttachmentsOptions options for listing release's attachments
type ListReleaseAttachmentsOptions struct {
ListOptions
}
// ListReleaseAttachments list release's attachments
func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt ListReleaseAttachmentsOptions) ([]*Attachment, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
attachments := make([]*Attachment, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET",
fmt.Sprintf("/repos/%s/%s/releases/%d/assets?%s", user, repo, release, opt.getURLQuery().Encode()),
nil, nil, &attachments)
return attachments, resp, err
}
// GetReleaseAttachment returns the requested attachment
func (c *Client) GetReleaseAttachment(user, repo string, release, id int64) (*Attachment, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
a := new(Attachment)
resp, err := c.getParsedResponse("GET",
fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id),
nil, nil, &a)
return a, resp, err
}
// CreateReleaseAttachment creates an attachment for the given release
func (c *Client) CreateReleaseAttachment(user, repo string, release int64, file io.Reader, filename string) (*Attachment, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
// Write file to body
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("attachment", filename)
if err != nil {
return nil, nil, err
}
if _, err = io.Copy(part, file); err != nil {
return nil, nil, err
}
if err = writer.Close(); err != nil {
return nil, nil, err
}
// Send request
attachment := new(Attachment)
resp, err := c.getParsedResponse("POST",
fmt.Sprintf("/repos/%s/%s/releases/%d/assets", user, repo, release),
http.Header{"Content-Type": {writer.FormDataContentType()}}, body, &attachment)
return attachment, resp, err
}
// EditAttachmentOptions options for editing attachments
type EditAttachmentOptions struct {
Name string `json:"name"`
}
// EditReleaseAttachment updates the given attachment with the given options
func (c *Client) EditReleaseAttachment(user, repo string, release, attachment int64, form EditAttachmentOptions) (*Attachment, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&form)
if err != nil {
return nil, nil, err
}
attach := new(Attachment)
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, attachment), jsonHeader, bytes.NewReader(body), attach)
return attach, resp, err
}
// DeleteReleaseAttachment deletes the given attachment including the uploaded file
func (c *Client) DeleteReleaseAttachment(user, repo string, release, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), nil, nil)
return resp, err
}

View file

@ -0,0 +1,499 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"github.com/hashicorp/go-version"
)
var jsonHeader = http.Header{"content-type": []string{"application/json"}}
// Version return the library version
func Version() string {
return "0.16.0"
}
// Client represents a thread-safe Gitea API client.
type Client struct {
url string
accessToken string
username string
password string
otp string
sudo string
userAgent string
debug bool
httpsigner *HTTPSign
client *http.Client
ctx context.Context
mutex sync.RWMutex
serverVersion *version.Version
getVersionOnce sync.Once
ignoreVersion bool // only set by SetGiteaVersion so don't need a mutex lock
}
// Response represents the gitea response
type Response struct {
*http.Response
FirstPage int
PrevPage int
NextPage int
LastPage int
}
// ClientOption are functions used to init a new client
type ClientOption func(*Client) error
// NewClient initializes and returns a API client.
// Usage of all gitea.Client methods is concurrency-safe.
func NewClient(url string, options ...ClientOption) (*Client, error) {
client := &Client{
url: strings.TrimSuffix(url, "/"),
client: &http.Client{},
ctx: context.Background(),
}
for _, opt := range options {
if err := opt(client); err != nil {
return nil, err
}
}
if err := client.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
if errors.Is(err, &ErrUnknownVersion{}) {
return client, err
}
return nil, err
}
return client, nil
}
// NewClientWithHTTP creates an API client with a custom http client
// Deprecated use SetHTTPClient option
func NewClientWithHTTP(url string, httpClient *http.Client) *Client {
client, _ := NewClient(url, SetHTTPClient(httpClient))
return client
}
// SetHTTPClient is an option for NewClient to set custom http client
func SetHTTPClient(httpClient *http.Client) ClientOption {
return func(client *Client) error {
client.SetHTTPClient(httpClient)
return nil
}
}
// SetHTTPClient replaces default http.Client with user given one.
func (c *Client) SetHTTPClient(client *http.Client) {
c.mutex.Lock()
c.client = client
c.mutex.Unlock()
}
// SetToken is an option for NewClient to set token
func SetToken(token string) ClientOption {
return func(client *Client) error {
client.mutex.Lock()
client.accessToken = token
client.mutex.Unlock()
return nil
}
}
// SetBasicAuth is an option for NewClient to set username and password
func SetBasicAuth(username, password string) ClientOption {
return func(client *Client) error {
client.SetBasicAuth(username, password)
return nil
}
}
// UseSSHCert is an option for NewClient to enable SSH certificate authentication via HTTPSign
// If you want to auth against the ssh-agent you'll need to set a principal, if you want to
// use a file on disk you'll need to specify sshKey.
// If you have an encrypted sshKey you'll need to also set the passphrase.
func UseSSHCert(principal, sshKey, passphrase string) ClientOption {
return func(client *Client) error {
if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil {
return err
}
client.mutex.Lock()
defer client.mutex.Unlock()
var err error
client.httpsigner, err = NewHTTPSignWithCert(principal, sshKey, passphrase)
if err != nil {
return err
}
return nil
}
}
// UseSSHPubkey is an option for NewClient to enable SSH pubkey authentication via HTTPSign
// If you want to auth against the ssh-agent you'll need to set a fingerprint, if you want to
// use a file on disk you'll need to specify sshKey.
// If you have an encrypted sshKey you'll need to also set the passphrase.
func UseSSHPubkey(fingerprint, sshKey, passphrase string) ClientOption {
return func(client *Client) error {
if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil {
return err
}
client.mutex.Lock()
defer client.mutex.Unlock()
var err error
client.httpsigner, err = NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase)
if err != nil {
return err
}
return nil
}
}
// SetBasicAuth sets username and password
func (c *Client) SetBasicAuth(username, password string) {
c.mutex.Lock()
c.username, c.password = username, password
c.mutex.Unlock()
}
// SetOTP is an option for NewClient to set OTP for 2FA
func SetOTP(otp string) ClientOption {
return func(client *Client) error {
client.SetOTP(otp)
return nil
}
}
// SetOTP sets OTP for 2FA
func (c *Client) SetOTP(otp string) {
c.mutex.Lock()
c.otp = otp
c.mutex.Unlock()
}
// SetContext is an option for NewClient to set the default context
func SetContext(ctx context.Context) ClientOption {
return func(client *Client) error {
client.SetContext(ctx)
return nil
}
}
// SetContext set default context witch is used for http requests
func (c *Client) SetContext(ctx context.Context) {
c.mutex.Lock()
c.ctx = ctx
c.mutex.Unlock()
}
// SetSudo is an option for NewClient to set sudo header
func SetSudo(sudo string) ClientOption {
return func(client *Client) error {
client.SetSudo(sudo)
return nil
}
}
// SetSudo sets username to impersonate.
func (c *Client) SetSudo(sudo string) {
c.mutex.Lock()
c.sudo = sudo
c.mutex.Unlock()
}
// SetUserAgent is an option for NewClient to set user-agent header
func SetUserAgent(userAgent string) ClientOption {
return func(client *Client) error {
client.SetUserAgent(userAgent)
return nil
}
}
// SetUserAgent sets the user-agent to send with every request.
func (c *Client) SetUserAgent(userAgent string) {
c.mutex.Lock()
c.userAgent = userAgent
c.mutex.Unlock()
}
// SetDebugMode is an option for NewClient to enable debug mode
func SetDebugMode() ClientOption {
return func(client *Client) error {
client.mutex.Lock()
client.debug = true
client.mutex.Unlock()
return nil
}
}
func newResponse(r *http.Response) *Response {
response := &Response{Response: r}
response.parseLinkHeader()
return response
}
func (r *Response) parseLinkHeader() {
link := r.Header.Get("Link")
if link == "" {
return
}
links := strings.Split(link, ",")
for _, l := range links {
u, param, ok := strings.Cut(l, ";")
if !ok {
continue
}
u = strings.Trim(u, " <>")
key, value, ok := strings.Cut(strings.TrimSpace(param), "=")
if !ok || key != "rel" {
continue
}
value = strings.Trim(value, "\"")
parsed, err := url.Parse(u)
if err != nil {
continue
}
page := parsed.Query().Get("page")
if page == "" {
continue
}
switch value {
case "first":
r.FirstPage, _ = strconv.Atoi(page)
case "prev":
r.PrevPage, _ = strconv.Atoi(page)
case "next":
r.NextPage, _ = strconv.Atoi(page)
case "last":
r.LastPage, _ = strconv.Atoi(page)
}
}
}
func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) {
c.mutex.RLock()
debug := c.debug
if debug {
fmt.Printf("%s: %s\nBody: %v\n", method, c.url+path, body)
}
req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body)
client := c.client // client ref can change from this point on so safe it
c.mutex.RUnlock()
if err != nil {
return nil, nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if debug {
fmt.Printf("Response: %v\n\n", resp)
}
return data, newResponse(resp), err
}
func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) {
c.mutex.RLock()
debug := c.debug
if debug {
var bodyStr string
if body != nil {
bs, _ := io.ReadAll(body)
body = bytes.NewReader(bs)
bodyStr = string(bs)
}
fmt.Printf("%s: %s\nHeader: %v\nBody: %s\n", method, c.url+"/api/v1"+path, header, bodyStr)
}
req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body)
if err != nil {
c.mutex.RUnlock()
return nil, err
}
if len(c.accessToken) != 0 {
req.Header.Set("Authorization", "token "+c.accessToken)
}
if len(c.otp) != 0 {
req.Header.Set("X-GITEA-OTP", c.otp)
}
if len(c.username) != 0 {
req.SetBasicAuth(c.username, c.password)
}
if len(c.sudo) != 0 {
req.Header.Set("Sudo", c.sudo)
}
if len(c.userAgent) != 0 {
req.Header.Set("User-Agent", c.userAgent)
}
client := c.client // client ref can change from this point on so safe it
c.mutex.RUnlock()
for k, v := range header {
req.Header[k] = v
}
if c.httpsigner != nil {
err = c.SignRequest(req)
if err != nil {
return nil, err
}
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if debug {
fmt.Printf("Response: %v\n\n", resp)
}
return newResponse(resp), nil
}
// Converts a response for a HTTP status code indicating an error condition
// (non-2XX) to a well-known error value and response body. For non-problematic
// (2XX) status codes nil will be returned. Note that on a non-2XX response, the
// response body stream will have been read and, hence, is closed on return.
func statusCodeToErr(resp *Response) (body []byte, err error) {
// no error
if resp.StatusCode/100 == 2 {
return nil, nil
}
//
// error: body will be read for details
//
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("body read on HTTP error %d: %v", resp.StatusCode, err)
}
// Try to unmarshal and get an error message
errMap := make(map[string]interface{})
if err = json.Unmarshal(data, &errMap); err != nil {
// when the JSON can't be parsed, data was probably empty or a
// plain string, so we try to return a helpful error anyway
path := resp.Request.URL.Path
method := resp.Request.Method
header := resp.Request.Header
return data, fmt.Errorf("Unknown API Error: %d\nRequest: '%s' with '%s' method '%s' header and '%s' body", resp.StatusCode, path, method, header, string(data))
}
if msg, ok := errMap["message"]; ok {
return data, fmt.Errorf("%v", msg)
}
// If no error message, at least give status and data
return data, fmt.Errorf("%s: %s", resp.Status, string(data))
}
func (c *Client) getResponseReader(method, path string, header http.Header, body io.Reader) (io.ReadCloser, *Response, error) {
resp, err := c.doRequest(method, path, header, body)
if err != nil {
return nil, resp, err
}
// check for errors
data, err := statusCodeToErr(resp)
if err != nil {
return io.NopCloser(bytes.NewReader(data)), resp, err
}
return resp.Body, resp, nil
}
func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, *Response, error) {
resp, err := c.doRequest(method, path, header, body)
if err != nil {
return nil, resp, err
}
defer resp.Body.Close()
// check for errors
data, err := statusCodeToErr(resp)
if err != nil {
return data, resp, err
}
// success (2XX), read body
data, err = io.ReadAll(resp.Body)
if err != nil {
return nil, resp, err
}
return data, resp, nil
}
func (c *Client) getParsedResponse(method, path string, header http.Header, body io.Reader, obj interface{}) (*Response, error) {
data, resp, err := c.getResponse(method, path, header, body)
if err != nil {
return resp, err
}
return resp, json.Unmarshal(data, obj)
}
func (c *Client) getStatusCode(method, path string, header http.Header, body io.Reader) (int, *Response, error) {
resp, err := c.doRequest(method, path, header, body)
if err != nil {
return -1, resp, err
}
defer resp.Body.Close()
return resp.StatusCode, resp, nil
}
// pathEscapeSegments escapes segments of a path while not escaping forward slash
func pathEscapeSegments(path string) string {
slice := strings.Split(path, "/")
for index := range slice {
slice[index] = url.PathEscape(slice[index])
}
escapedPath := strings.Join(slice, "/")
return escapedPath
}
// escapeValidatePathSegments is a help function to validate and encode url path segments
func escapeValidatePathSegments(seg ...*string) error {
for i := range seg {
if seg[i] == nil || len(*seg[i]) == 0 {
return fmt.Errorf("path segment [%d] is empty", i)
}
*seg[i] = url.PathEscape(*seg[i])
}
return nil
}

View file

@ -0,0 +1,51 @@
// Copyright 2016 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// ListForksOptions options for listing repository's forks
type ListForksOptions struct {
ListOptions
}
// ListForks list a repository's forks
func (c *Client) ListForks(user, repo string, opt ListForksOptions) ([]*Repository, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
forks := make([]*Repository, opt.PageSize)
resp, err := c.getParsedResponse("GET",
fmt.Sprintf("/repos/%s/%s/forks?%s", user, repo, opt.getURLQuery().Encode()),
nil, nil, &forks)
return forks, resp, err
}
// CreateForkOption options for creating a fork
type CreateForkOption struct {
// organization name, if forking into an organization
Organization *string `json:"organization"`
// name of the forked repository
Name *string `json:"name"`
}
// CreateFork create a fork of a repository
func (c *Client) CreateFork(user, repo string, form CreateForkOption) (*Repository, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
body, err := json.Marshal(form)
if err != nil {
return nil, nil, err
}
fork := new(Repository)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/forks", user, repo), jsonHeader, bytes.NewReader(body), &fork)
return fork, resp, err
}

View file

@ -0,0 +1,28 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
)
// GitBlobResponse represents a git blob
type GitBlobResponse struct {
Content string `json:"content"`
Encoding string `json:"encoding"`
URL string `json:"url"`
SHA string `json:"sha"`
Size int64 `json:"size"`
}
// GetBlob get the blob of a repository file
func (c *Client) GetBlob(user, repo, sha string) (*GitBlobResponse, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil {
return nil, nil, err
}
blob := new(GitBlobResponse)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/blobs/%s", user, repo, sha), nil, nil, blob)
return blob, resp, err
}

View file

@ -0,0 +1,71 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// GitHook represents a Git repository hook
type GitHook struct {
Name string `json:"name"`
IsActive bool `json:"is_active"`
Content string `json:"content,omitempty"`
}
// ListRepoGitHooksOptions options for listing repository's githooks
type ListRepoGitHooksOptions struct {
ListOptions
}
// ListRepoGitHooks list all the Git hooks of one repository
func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions) ([]*GitHook, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
hooks := make([]*GitHook, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks)
return hooks, resp, err
}
// GetRepoGitHook get a Git hook of a repository
func (c *Client) GetRepoGitHook(user, repo, id string) (*GitHook, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
return nil, nil, err
}
h := new(GitHook)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil, h)
return h, resp, err
}
// EditGitHookOption options when modifying one Git hook
type EditGitHookOption struct {
Content string `json:"content"`
}
// EditRepoGitHook modify one Git hook of a repository
func (c *Client) EditRepoGitHook(user, repo, id string, opt EditGitHookOption) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), jsonHeader, bytes.NewReader(body))
return resp, err
}
// DeleteRepoGitHook delete one Git hook from a repository
func (c *Client) DeleteRepoGitHook(user, repo, id string) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil)
return resp, err
}

View file

@ -0,0 +1,38 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package sdk
import (
"fmt"
"time"
)
type Topic struct {
ID int64 `json:"id"`
Name string `json:"topic_name"`
RepoCount int `json:"repo_count"`
Created *time.Time `json:"created_at"`
Updated *time.Time `json:"updated_at"`
}
type SearchTopicsOptions struct {
ListOptions
Keyword string
}
func (opt *SearchTopicsOptions) QueryEncode() string {
query := opt.getURLQuery()
if opt.Keyword != "" {
query.Add("q", opt.Keyword)
}
return query.Encode()
}
func (c *Client) SearchTopics(opt SearchTopicsOptions) ([]*Topic, *Response, error) {
opt.setDefaults()
topics := make([]*Topic, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/topics/search?%s", opt.getURLQuery().Encode()), nil, nil, &topics)
return topics, resp, err
}

View file

@ -0,0 +1,20 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
// OptionalBool convert a bool to a bool reference
func OptionalBool(v bool) *bool {
return &v
}
// OptionalString convert a string to a string reference
func OptionalString(v string) *string {
return &v
}
// OptionalInt64 convert a int64 to a int64 reference
func OptionalInt64(v int64) *int64 {
return &v
}

196
forges/forgejo/sdk/hook.go Normal file
View file

@ -0,0 +1,196 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"time"
)
// Hook a hook is a web hook when one repository changed
type Hook struct {
ID int64 `json:"id"`
Type string `json:"type"`
URL string `json:"-"`
Config map[string]string `json:"config"`
Events []string `json:"events"`
Active bool `json:"active"`
Updated time.Time `json:"updated_at"`
Created time.Time `json:"created_at"`
}
// HookType represent all webhook types gitea currently offer
type HookType string
const (
// HookTypeDingtalk webhook that dingtalk understand
HookTypeDingtalk HookType = "dingtalk"
// HookTypeDiscord webhook that discord understand
HookTypeDiscord HookType = "discord"
// HookTypeGitea webhook that gitea understand
HookTypeGitea HookType = "gitea"
// HookTypeGogs webhook that gogs understand
HookTypeGogs HookType = "gogs"
// HookTypeMsteams webhook that msteams understand
HookTypeMsteams HookType = "msteams"
// HookTypeSlack webhook that slack understand
HookTypeSlack HookType = "slack"
// HookTypeTelegram webhook that telegram understand
HookTypeTelegram HookType = "telegram"
// HookTypeFeishu webhook that feishu understand
HookTypeFeishu HookType = "feishu"
)
// ListHooksOptions options for listing hooks
type ListHooksOptions struct {
ListOptions
}
// ListOrgHooks list all the hooks of one organization
func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
opt.setDefaults()
hooks := make([]*Hook, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks?%s", org, opt.getURLQuery().Encode()), nil, nil, &hooks)
return hooks, resp, err
}
// ListRepoHooks list all the hooks of one repository
func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
hooks := make([]*Hook, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks)
return hooks, resp, err
}
// GetOrgHook get a hook of an organization
func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
h := new(Hook)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil, h)
return h, resp, err
}
// GetRepoHook get a hook of a repository
func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
h := new(Hook)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil, h)
return h, resp, err
}
// CreateHookOption options when create a hook
type CreateHookOption struct {
Type HookType `json:"type"`
Config map[string]string `json:"config"`
Events []string `json:"events"`
BranchFilter string `json:"branch_filter"`
Active bool `json:"active"`
AuthorizationHeader string `json:"authorization_header"`
}
// Validate the CreateHookOption struct
func (opt CreateHookOption) Validate() error {
if len(opt.Type) == 0 {
return fmt.Errorf("hook type needed")
}
return nil
}
// CreateOrgHook create one hook for an organization, with options
func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
h := new(Hook)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/hooks", org), jsonHeader, bytes.NewReader(body), h)
return h, resp, err
}
// CreateRepoHook create one hook for a repository, with options
func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
h := new(Hook)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/hooks", user, repo), jsonHeader, bytes.NewReader(body), h)
return h, resp, err
}
// EditHookOption options when modify one hook
type EditHookOption struct {
Config map[string]string `json:"config"`
Events []string `json:"events"`
BranchFilter string `json:"branch_filter"`
Active *bool `json:"active"`
AuthorizationHeader string `json:"authorization_header"`
}
// EditOrgHook modify one hook of an organization, with hook id and options
func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), jsonHeader, bytes.NewReader(body))
return resp, err
}
// EditRepoHook modify one hook of a repository, with hook id and options
func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), jsonHeader, bytes.NewReader(body))
return resp, err
}
// DeleteOrgHook delete one hook from an organization, with hook id
func (c *Client) DeleteOrgHook(org string, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil)
return resp, err
}
// DeleteRepoHook delete one hook from a repository, with hook id
func (c *Client) DeleteRepoHook(user, repo string, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil)
return resp, err
}

View file

@ -0,0 +1,59 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
)
// VerifyWebhookSignature verifies that a payload matches the X-Gitea-Signature based on a secret
func VerifyWebhookSignature(secret, expected string, payload []byte) (bool, error) {
hash := hmac.New(sha256.New, []byte(secret))
if _, err := hash.Write(payload); err != nil {
return false, err
}
expectedSum, err := hex.DecodeString(expected)
if err != nil {
return false, err
}
return hmac.Equal(hash.Sum(nil), expectedSum), nil
}
// VerifyWebhookSignatureMiddleware is a http.Handler for verifying X-Gitea-Signature on incoming webhooks
func VerifyWebhookSignatureMiddleware(secret string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var b bytes.Buffer
if _, err := io.Copy(&b, r.Body); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
expected := r.Header.Get("X-Gitea-Signature")
if expected == "" {
http.Error(w, "no signature found", http.StatusBadRequest)
return
}
ok, err := VerifyWebhookSignature(secret, expected, b.Bytes())
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
if !ok {
http.Error(w, "invalid payload", http.StatusUnauthorized)
return
}
r.Body = io.NopCloser(&b)
next.ServeHTTP(w, r)
})
}
}

View file

@ -0,0 +1,253 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"crypto"
"encoding/base64"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/42wim/httpsig"
"golang.org/x/crypto/ssh"
)
// HTTPSign contains the signer used for signing requests
type HTTPSign struct {
ssh.Signer
cert bool
}
// HTTPSignConfig contains the configuration for creating a HTTPSign
type HTTPSignConfig struct {
fingerprint string
principal string
pubkey bool
cert bool
sshKey string
passphrase string
}
// NewHTTPSignWithPubkey can be used to create a HTTPSign with a public key
// if no fingerprint is specified it returns the first public key found
func NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase string) (*HTTPSign, error) {
return newHTTPSign(&HTTPSignConfig{
fingerprint: fingerprint,
pubkey: true,
sshKey: sshKey,
passphrase: passphrase,
})
}
// NewHTTPSignWithCert can be used to create a HTTPSign with a certificate
// if no principal is specified it returns the first certificate found
func NewHTTPSignWithCert(principal, sshKey, passphrase string) (*HTTPSign, error) {
return newHTTPSign(&HTTPSignConfig{
principal: principal,
cert: true,
sshKey: sshKey,
passphrase: passphrase,
})
}
// NewHTTPSign returns a new HTTPSign
// It will check the ssh-agent or a local file is config.sshKey is set.
// Depending on the configuration it will either use a certificate or a public key
func newHTTPSign(config *HTTPSignConfig) (*HTTPSign, error) {
var signer ssh.Signer
if config.sshKey != "" {
priv, err := os.ReadFile(config.sshKey)
if err != nil {
return nil, err
}
if config.passphrase == "" {
signer, err = ssh.ParsePrivateKey(priv)
if err != nil {
return nil, err
}
} else {
signer, err = ssh.ParsePrivateKeyWithPassphrase(priv, []byte(config.passphrase))
if err != nil {
return nil, err
}
}
if config.cert {
certbytes, err := os.ReadFile(config.sshKey + "-cert.pub")
if err != nil {
return nil, err
}
pub, _, _, _, err := ssh.ParseAuthorizedKey(certbytes)
if err != nil {
return nil, err
}
cert, ok := pub.(*ssh.Certificate)
if !ok {
return nil, fmt.Errorf("failed to parse certificate")
}
signer, err = ssh.NewCertSigner(cert, signer)
if err != nil {
return nil, err
}
}
} else {
// if no sshKey is specified, check if we have a ssh-agent and use it
agent, err := GetAgent()
if err != nil {
return nil, err
}
signers, err := agent.Signers()
if err != nil {
return nil, err
}
if len(signers) == 0 {
return nil, fmt.Errorf("no signers found")
}
if config.cert {
signer = findCertSigner(signers, config.principal)
if signer == nil {
return nil, fmt.Errorf("no certificate found for %s", config.principal)
}
}
if config.pubkey {
signer = findPubkeySigner(signers, config.fingerprint)
if signer == nil {
return nil, fmt.Errorf("no public key found for %s", config.fingerprint)
}
}
}
return &HTTPSign{
Signer: signer,
cert: config.cert,
}, nil
}
// SignRequest signs a HTTP request
func (c *Client) SignRequest(r *http.Request) error {
var contents []byte
headersToSign := []string{httpsig.RequestTarget, "(created)", "(expires)"}
if c.httpsigner.cert {
// add our certificate to the headers to sign
pubkey, _ := ssh.ParsePublicKey(c.httpsigner.Signer.PublicKey().Marshal())
if cert, ok := pubkey.(*ssh.Certificate); ok {
certString := base64.RawStdEncoding.EncodeToString(cert.Marshal())
r.Header.Add("x-ssh-certificate", certString)
headersToSign = append(headersToSign, "x-ssh-certificate")
} else {
return fmt.Errorf("no ssh certificate found")
}
}
// if we have a body, the Digest header will be added and we'll include this also in
// our signature.
if r.Body != nil {
body, err := r.GetBody()
if err != nil {
return fmt.Errorf("getBody() failed: %s", err)
}
contents, err = io.ReadAll(body)
if err != nil {
return fmt.Errorf("failed reading body: %s", err)
}
headersToSign = append(headersToSign, "Digest")
}
// create a signer for the request and headers, the signature will be valid for 10 seconds
signer, _, err := httpsig.NewSSHSigner(c.httpsigner.Signer, httpsig.DigestSha512, headersToSign, httpsig.Signature, 10)
if err != nil {
return fmt.Errorf("httpsig.NewSSHSigner failed: %s", err)
}
// sign the request, use the fingerprint if we don't have a certificate
keyID := "gitea"
if !c.httpsigner.cert {
keyID = ssh.FingerprintSHA256(c.httpsigner.Signer.PublicKey())
}
err = signer.SignRequest(keyID, r, contents)
if err != nil {
return fmt.Errorf("httpsig.Signrequest failed: %s", err)
}
return nil
}
// findCertSigner returns the Signer containing a valid certificate
// if no principal is specified it returns the first certificate found
func findCertSigner(sshsigners []ssh.Signer, principal string) ssh.Signer {
for _, s := range sshsigners {
// Check if the key is a certificate
if !strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") {
continue
}
// convert the ssh.Signer to a ssh.Certificate
mpubkey, _ := ssh.ParsePublicKey(s.PublicKey().Marshal())
cryptopub := mpubkey.(crypto.PublicKey)
cert := cryptopub.(*ssh.Certificate)
t := time.Unix(int64(cert.ValidBefore), 0)
// make sure the certificate is at least 10 seconds valid
if time.Until(t) <= time.Second*10 {
continue
}
if principal == "" {
return s
}
for _, p := range cert.ValidPrincipals {
if p == principal {
return s
}
}
}
return nil
}
// findPubkeySigner returns the Signer containing a valid public key
// if no fingerprint is specified it returns the first public key found
func findPubkeySigner(sshsigners []ssh.Signer, fingerprint string) ssh.Signer {
for _, s := range sshsigners {
// Check if the key is a certificate
if strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") {
continue
}
if fingerprint == "" {
return s
}
if strings.TrimSpace(string(ssh.MarshalAuthorizedKey(s.PublicKey()))) == fingerprint {
return s
}
if ssh.FingerprintSHA256(s.PublicKey()) == fingerprint {
return s
}
}
return nil
}

309
forges/forgejo/sdk/issue.go Normal file
View file

@ -0,0 +1,309 @@
// Copyright 2016 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"strings"
"time"
)
// PullRequestMeta PR info if an issue is a PR
type PullRequestMeta struct {
HasMerged bool `json:"merged"`
Merged *time.Time `json:"merged_at"`
}
// RepositoryMeta basic repository information
type RepositoryMeta struct {
ID int64 `json:"id"`
Name string `json:"name"`
Owner string `json:"owner"`
FullName string `json:"full_name"`
}
// Issue represents an issue in a repository
type Issue struct {
ID int64 `json:"id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
Index int64 `json:"number"`
Poster *User `json:"user"`
OriginalAuthor string `json:"original_author"`
OriginalAuthorID int64 `json:"original_author_id"`
Title string `json:"title"`
Body string `json:"body"`
Ref string `json:"ref"`
Labels []*Label `json:"labels"`
Milestone *Milestone `json:"milestone"`
Assignees []*User `json:"assignees"`
// Whether the issue is open or closed
State StateType `json:"state"`
IsLocked bool `json:"is_locked"`
Comments int `json:"comments"`
Created time.Time `json:"created_at"`
Updated time.Time `json:"updated_at"`
Closed *time.Time `json:"closed_at"`
Deadline *time.Time `json:"due_date"`
PullRequest *PullRequestMeta `json:"pull_request"`
Repository *RepositoryMeta `json:"repository"`
}
// ListIssueOption list issue options
type ListIssueOption struct {
ListOptions
State StateType
Type IssueType
Labels []string
Milestones []string
KeyWord string
Since time.Time
Before time.Time
// filter by created by username
CreatedBy string
// filter by assigned to username
AssignedBy string
// filter by username mentioned
MentionedBy string
// filter by owner (only works on ListIssues on User)
Owner string
// filter by team (requires organization owner parameter to be provided and only works on ListIssues on User)
Team string
}
// StateType issue state type
type StateType string
const (
// StateOpen pr/issue is opend
StateOpen StateType = "open"
// StateClosed pr/issue is closed
StateClosed StateType = "closed"
// StateAll is all
StateAll StateType = "all"
)
// IssueType is issue a pull or only an issue
type IssueType string
const (
// IssueTypeAll pr and issue
IssueTypeAll IssueType = ""
// IssueTypeIssue only issues
IssueTypeIssue IssueType = "issues"
// IssueTypePull only pulls
IssueTypePull IssueType = "pulls"
)
// QueryEncode turns options into querystring argument
func (opt *ListIssueOption) QueryEncode() string {
query := opt.getURLQuery()
if len(opt.State) > 0 {
query.Add("state", string(opt.State))
}
if len(opt.Labels) > 0 {
query.Add("labels", strings.Join(opt.Labels, ","))
}
if len(opt.KeyWord) > 0 {
query.Add("q", opt.KeyWord)
}
query.Add("type", string(opt.Type))
if len(opt.Milestones) > 0 {
query.Add("milestones", strings.Join(opt.Milestones, ","))
}
if !opt.Since.IsZero() {
query.Add("since", opt.Since.Format(time.RFC3339))
}
if !opt.Before.IsZero() {
query.Add("before", opt.Before.Format(time.RFC3339))
}
if len(opt.CreatedBy) > 0 {
query.Add("created_by", opt.CreatedBy)
}
if len(opt.AssignedBy) > 0 {
query.Add("assigned_by", opt.AssignedBy)
}
if len(opt.MentionedBy) > 0 {
query.Add("mentioned_by", opt.MentionedBy)
}
if len(opt.Owner) > 0 {
query.Add("owner", opt.Owner)
}
if len(opt.Team) > 0 {
query.Add("team", opt.MentionedBy)
}
return query.Encode()
}
// ListIssues returns all issues assigned the authenticated user
func (c *Client) ListIssues(opt ListIssueOption) ([]*Issue, *Response, error) {
opt.setDefaults()
issues := make([]*Issue, 0, opt.PageSize)
link, _ := url.Parse("/repos/issues/search")
link.RawQuery = opt.QueryEncode()
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues)
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil {
for i := 0; i < len(issues); i++ {
if issues[i].Repository != nil {
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
}
}
}
for i := range issues {
c.issueBackwardsCompatibility(issues[i])
}
return issues, resp, err
}
// ListRepoIssues returns all issues for a given repository
func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Issue, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
issues := make([]*Issue, 0, opt.PageSize)
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues", owner, repo))
link.RawQuery = opt.QueryEncode()
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues)
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil {
for i := 0; i < len(issues); i++ {
if issues[i].Repository != nil {
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
}
}
}
for i := range issues {
c.issueBackwardsCompatibility(issues[i])
}
return issues, resp, err
}
// GetIssue returns a single issue for a given repository
func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
issue := new(Issue)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), nil, nil, issue)
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil && issue.Repository != nil {
issue.Repository.Owner = strings.Split(issue.Repository.FullName, "/")[0]
}
c.issueBackwardsCompatibility(issue)
return issue, resp, err
}
// CreateIssueOption options to create one issue
type CreateIssueOption struct {
Title string `json:"title"`
Body string `json:"body"`
Ref string `json:"ref"`
Assignees []string `json:"assignees"`
Deadline *time.Time `json:"due_date"`
// milestone id
Milestone int64 `json:"milestone"`
// list of label ids
Labels []int64 `json:"labels"`
Closed bool `json:"closed"`
}
// Validate the CreateIssueOption struct
func (opt CreateIssueOption) Validate() error {
if len(strings.TrimSpace(opt.Title)) == 0 {
return fmt.Errorf("title is empty")
}
return nil
}
// CreateIssue create a new issue for a given repository
func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
issue := new(Issue)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo),
jsonHeader, bytes.NewReader(body), issue)
c.issueBackwardsCompatibility(issue)
return issue, resp, err
}
// EditIssueOption options for editing an issue
type EditIssueOption struct {
Title string `json:"title"`
Body *string `json:"body"`
Ref *string `json:"ref"`
Assignees []string `json:"assignees"`
Milestone *int64 `json:"milestone"`
State *StateType `json:"state"`
Deadline *time.Time `json:"due_date"`
RemoveDeadline *bool `json:"unset_due_date"`
}
// Validate the EditIssueOption struct
func (opt EditIssueOption) Validate() error {
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 {
return fmt.Errorf("title is empty")
}
return nil
}
// EditIssue modify an existing issue for a given repository
func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption) (*Issue, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
issue := new(Issue)
resp, err := c.getParsedResponse("PATCH",
fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index),
jsonHeader, bytes.NewReader(body), issue)
c.issueBackwardsCompatibility(issue)
return issue, resp, err
}
// DeleteIssue delete a issue from a repository
func (c *Client) DeleteIssue(user, repo string, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE",
fmt.Sprintf("/repos/%s/%s/issues/%d", user, repo, id),
nil, nil)
return resp, err
}
func (c *Client) issueBackwardsCompatibility(issue *Issue) {
if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil {
c.mutex.RLock()
issue.HTMLURL = fmt.Sprintf("%s/%s/issues/%d", c.url, issue.Repository.FullName, issue.Index)
c.mutex.RUnlock()
}
}

View file

@ -0,0 +1,154 @@
// Copyright 2016 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"time"
)
// Comment represents a comment on a commit or issue
type Comment struct {
ID int64 `json:"id"`
HTMLURL string `json:"html_url"`
PRURL string `json:"pull_request_url"`
IssueURL string `json:"issue_url"`
Poster *User `json:"user"`
OriginalAuthor string `json:"original_author"`
OriginalAuthorID int64 `json:"original_author_id"`
Body string `json:"body"`
Created time.Time `json:"created_at"`
Updated time.Time `json:"updated_at"`
}
// ListIssueCommentOptions list comment options
type ListIssueCommentOptions struct {
ListOptions
Since time.Time
Before time.Time
}
// QueryEncode turns options into querystring argument
func (opt *ListIssueCommentOptions) QueryEncode() string {
query := opt.getURLQuery()
if !opt.Since.IsZero() {
query.Add("since", opt.Since.Format(time.RFC3339))
}
if !opt.Before.IsZero() {
query.Add("before", opt.Before.Format(time.RFC3339))
}
return query.Encode()
}
// ListIssueComments list comments on an issue.
func (c *Client) ListIssueComments(owner, repo string, index int64, opt ListIssueCommentOptions) ([]*Comment, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index))
link.RawQuery = opt.QueryEncode()
comments := make([]*Comment, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments)
return comments, resp, err
}
// ListRepoIssueComments list comments for a given repo.
func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentOptions) ([]*Comment, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo))
link.RawQuery = opt.QueryEncode()
comments := make([]*Comment, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments)
return comments, resp, err
}
// GetIssueComment get a comment for a given repo by id.
func (c *Client) GetIssueComment(owner, repo string, id int64) (*Comment, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
comment := new(Comment)
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return comment, nil, err
}
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, id), nil, nil, &comment)
return comment, resp, err
}
// CreateIssueCommentOption options for creating a comment on an issue
type CreateIssueCommentOption struct {
Body string `json:"body"`
}
// Validate the CreateIssueCommentOption struct
func (opt CreateIssueCommentOption) Validate() error {
if len(opt.Body) == 0 {
return fmt.Errorf("body is empty")
}
return nil
}
// CreateIssueComment create comment on an issue.
func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
comment := new(Comment)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), jsonHeader, bytes.NewReader(body), comment)
return comment, resp, err
}
// EditIssueCommentOption options for editing a comment
type EditIssueCommentOption struct {
Body string `json:"body"`
}
// Validate the EditIssueCommentOption struct
func (opt EditIssueCommentOption) Validate() error {
if len(opt.Body) == 0 {
return fmt.Errorf("body is empty")
}
return nil
}
// EditIssueComment edits an issue comment.
func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditIssueCommentOption) (*Comment, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
comment := new(Comment)
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), jsonHeader, bytes.NewReader(body), comment)
return comment, resp, err
}
// DeleteIssueComment deletes an issue comment.
func (c *Client) DeleteIssueComment(owner, repo string, commentID int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), nil, nil)
return resp, err
}

View file

@ -0,0 +1,211 @@
// Copyright 2016 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"regexp"
"strings"
)
// Label a label to an issue or a pr
type Label struct {
ID int64 `json:"id"`
Name string `json:"name"`
// example: 00aabb
Color string `json:"color"`
Description string `json:"description"`
URL string `json:"url"`
}
// ListLabelsOptions options for listing repository's labels
type ListLabelsOptions struct {
ListOptions
}
// ListRepoLabels list labels of one repository
func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*Label, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
labels := make([]*Label, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels?%s", owner, repo, opt.getURLQuery().Encode()), nil, nil, &labels)
return labels, resp, err
}
// GetRepoLabel get one label of repository by repo it
func (c *Client) GetRepoLabel(owner, repo string, id int64) (*Label, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
label := new(Label)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil, label)
return label, resp, err
}
// CreateLabelOption options for creating a label
type CreateLabelOption struct {
Name string `json:"name"`
// example: #00aabb
Color string `json:"color"`
Description string `json:"description"`
}
// Validate the CreateLabelOption struct
func (opt CreateLabelOption) Validate() error {
aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", opt.Color)
if err != nil {
return err
}
if !aw {
return fmt.Errorf("invalid color format")
}
if len(strings.TrimSpace(opt.Name)) == 0 {
return fmt.Errorf("empty name not allowed")
}
return nil
}
// CreateLabel create one label of repository
func (c *Client) CreateLabel(owner, repo string, opt CreateLabelOption) (*Label, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
if len(opt.Color) == 6 {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
opt.Color = "#" + opt.Color
}
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
label := new(Label)
resp, err := c.getParsedResponse("POST",
fmt.Sprintf("/repos/%s/%s/labels", owner, repo),
jsonHeader, bytes.NewReader(body), label)
return label, resp, err
}
// EditLabelOption options for editing a label
type EditLabelOption struct {
Name *string `json:"name"`
Color *string `json:"color"`
Description *string `json:"description"`
}
// Validate the EditLabelOption struct
func (opt EditLabelOption) Validate() error {
if opt.Color != nil {
aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", *opt.Color)
if err != nil {
return err
}
if !aw {
return fmt.Errorf("invalid color format")
}
}
if opt.Name != nil {
if len(strings.TrimSpace(*opt.Name)) == 0 {
return fmt.Errorf("empty name not allowed")
}
}
return nil
}
// EditLabel modify one label with options
func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*Label, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
label := new(Label)
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), label)
return label, resp, err
}
// DeleteLabel delete one label of repository by id
func (c *Client) DeleteLabel(owner, repo string, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil)
return resp, err
}
// GetIssueLabels get labels of one issue via issue id
func (c *Client) GetIssueLabels(owner, repo string, index int64, opts ListLabelsOptions) ([]*Label, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
labels := make([]*Label, 0, 5)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/labels?%s", owner, repo, index, opts.getURLQuery().Encode()), nil, nil, &labels)
return labels, resp, err
}
// IssueLabelsOption a collection of labels
type IssueLabelsOption struct {
// list of label IDs
Labels []int64 `json:"labels"`
}
// AddIssueLabels add one or more labels to one issue
func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
var labels []*Label
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels)
return labels, resp, err
}
// ReplaceIssueLabels replace old labels of issue with new labels
func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
var labels []*Label
resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels)
return labels, resp, err
}
// DeleteIssueLabel delete one label of one issue by issue id and label id
// TODO: maybe we need delete by label name and issue id
func (c *Client) DeleteIssueLabel(owner, repo string, index, label int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, label), nil, nil)
return resp, err
}
// ClearIssueLabels delete all the labels of one issue.
func (c *Client) ClearIssueLabels(owner, repo string, index int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), nil, nil)
return resp, err
}

View file

@ -0,0 +1,237 @@
// Copyright 2016 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"strings"
"time"
)
// Milestone milestone is a collection of issues on one repository
type Milestone struct {
ID int64 `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
State StateType `json:"state"`
OpenIssues int `json:"open_issues"`
ClosedIssues int `json:"closed_issues"`
Created time.Time `json:"created_at"`
Updated *time.Time `json:"updated_at"`
Closed *time.Time `json:"closed_at"`
Deadline *time.Time `json:"due_on"`
}
// ListMilestoneOption list milestone options
type ListMilestoneOption struct {
ListOptions
// open, closed, all
State StateType
Name string
}
// QueryEncode turns options into querystring argument
func (opt *ListMilestoneOption) QueryEncode() string {
query := opt.getURLQuery()
if opt.State != "" {
query.Add("state", string(opt.State))
}
if len(opt.Name) != 0 {
query.Add("name", opt.Name)
}
return query.Encode()
}
// ListRepoMilestones list all the milestones of one repository
func (c *Client) ListRepoMilestones(owner, repo string, opt ListMilestoneOption) ([]*Milestone, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
milestones := make([]*Milestone, 0, opt.PageSize)
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/milestones", owner, repo))
link.RawQuery = opt.QueryEncode()
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &milestones)
return milestones, resp, err
}
// GetMilestone get one milestone by repo name and milestone id
func (c *Client) GetMilestone(owner, repo string, id int64) (*Milestone, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
milestone := new(Milestone)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil, milestone)
return milestone, resp, err
}
// GetMilestoneByName get one milestone by repo and milestone name
func (c *Client) GetMilestoneByName(owner, repo, name string) (*Milestone, *Response, error) {
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
// backwards compatibility mode
m, resp, err := c.resolveMilestoneByName(owner, repo, name)
return m, resp, err
}
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
return nil, nil, err
}
milestone := new(Milestone)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil, milestone)
return milestone, resp, err
}
// CreateMilestoneOption options for creating a milestone
type CreateMilestoneOption struct {
Title string `json:"title"`
Description string `json:"description"`
State StateType `json:"state"`
Deadline *time.Time `json:"due_on"`
}
// Validate the CreateMilestoneOption struct
func (opt CreateMilestoneOption) Validate() error {
if len(strings.TrimSpace(opt.Title)) == 0 {
return fmt.Errorf("title is empty")
}
return nil
}
// CreateMilestone create one milestone with options
func (c *Client) CreateMilestone(owner, repo string, opt CreateMilestoneOption) (*Milestone, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
milestone := new(Milestone)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/milestones", owner, repo), jsonHeader, bytes.NewReader(body), milestone)
// make creating closed milestones need gitea >= v1.13.0
// this make it backwards compatible
if err == nil && opt.State == StateClosed && milestone.State != StateClosed {
closed := StateClosed
return c.EditMilestone(owner, repo, milestone.ID, EditMilestoneOption{
State: &closed,
})
}
return milestone, resp, err
}
// EditMilestoneOption options for editing a milestone
type EditMilestoneOption struct {
Title string `json:"title"`
Description *string `json:"description"`
State *StateType `json:"state"`
Deadline *time.Time `json:"due_on"`
}
// Validate the EditMilestoneOption struct
func (opt EditMilestoneOption) Validate() error {
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 {
return fmt.Errorf("title is empty")
}
return nil
}
// EditMilestone modify milestone with options
func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOption) (*Milestone, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
milestone := new(Milestone)
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), milestone)
return milestone, resp, err
}
// EditMilestoneByName modify milestone with options
func (c *Client) EditMilestoneByName(owner, repo, name string, opt EditMilestoneOption) (*Milestone, *Response, error) {
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
// backwards compatibility mode
m, _, err := c.resolveMilestoneByName(owner, repo, name)
if err != nil {
return nil, nil, err
}
return c.EditMilestone(owner, repo, m.ID, opt)
}
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
milestone := new(Milestone)
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), milestone)
return milestone, resp, err
}
// DeleteMilestone delete one milestone by id
func (c *Client) DeleteMilestone(owner, repo string, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil)
return resp, err
}
// DeleteMilestoneByName delete one milestone by name
func (c *Client) DeleteMilestoneByName(owner, repo, name string) (*Response, error) {
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
// backwards compatibility mode
m, _, err := c.resolveMilestoneByName(owner, repo, name)
if err != nil {
return nil, err
}
return c.DeleteMilestone(owner, repo, m.ID)
}
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil)
return resp, err
}
// resolveMilestoneByName is a fallback method to find milestone id by name
func (c *Client) resolveMilestoneByName(owner, repo, name string) (*Milestone, *Response, error) {
for i := 1; ; i++ {
miles, resp, err := c.ListRepoMilestones(owner, repo, ListMilestoneOption{
ListOptions: ListOptions{
Page: i,
},
State: "all",
})
if err != nil {
return nil, nil, err
}
if len(miles) == 0 {
return nil, nil, fmt.Errorf("milestone '%s' do not exist", name)
}
for _, m := range miles {
if strings.EqualFold(strings.TrimSpace(m.Title), strings.TrimSpace(name)) {
return m, resp, nil
}
}
}
}

View file

@ -0,0 +1,104 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"time"
)
// Reaction contain one reaction
type Reaction struct {
User *User `json:"user"`
Reaction string `json:"content"`
Created time.Time `json:"created_at"`
}
// GetIssueReactions get a list reactions of an issue
func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
reactions := make([]*Reaction, 0, 10)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), nil, nil, &reactions)
return reactions, resp, err
}
// GetIssueCommentReactions get a list of reactions from a comment of an issue
func (c *Client) GetIssueCommentReactions(owner, repo string, commentID int64) ([]*Reaction, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
reactions := make([]*Reaction, 0, 10)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), nil, nil, &reactions)
return reactions, resp, err
}
// editReactionOption contain the reaction type
type editReactionOption struct {
Reaction string `json:"content"`
}
// PostIssueReaction add a reaction to an issue
func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction string) (*Reaction, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
reactionResponse := new(Reaction)
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
if err != nil {
return nil, nil, err
}
resp, err := c.getParsedResponse("POST",
fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index),
jsonHeader, bytes.NewReader(body), reactionResponse)
return reactionResponse, resp, err
}
// DeleteIssueReaction remove a reaction from an issue
func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction string) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), jsonHeader, bytes.NewReader(body))
return resp, err
}
// PostIssueCommentReaction add a reaction to a comment of an issue
func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Reaction, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
reactionResponse := new(Reaction)
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
if err != nil {
return nil, nil, err
}
resp, err := c.getParsedResponse("POST",
fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID),
jsonHeader, bytes.NewReader(body), reactionResponse)
return reactionResponse, resp, err
}
// DeleteIssueCommentReaction remove a reaction from a comment of an issue
func (c *Client) DeleteIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE",
fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID),
jsonHeader, bytes.NewReader(body))
return resp, err
}

View file

@ -0,0 +1,57 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"time"
)
// StopWatch represents a running stopwatch of an issue / pr
type StopWatch struct {
Created time.Time `json:"created"`
Seconds int64 `json:"seconds"`
Duration string `json:"duration"`
IssueIndex int64 `json:"issue_index"`
IssueTitle string `json:"issue_title"`
RepoOwnerName string `json:"repo_owner_name"`
RepoName string `json:"repo_name"`
}
// GetMyStopwatches list all stopwatches
func (c *Client) GetMyStopwatches() ([]*StopWatch, *Response, error) {
stopwatches := make([]*StopWatch, 0, 1)
resp, err := c.getParsedResponse("GET", "/user/stopwatches", nil, nil, &stopwatches)
return stopwatches, resp, err
}
// DeleteIssueStopwatch delete / cancel a specific stopwatch
func (c *Client) DeleteIssueStopwatch(owner, repo string, index int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/delete", owner, repo, index), nil, nil)
return resp, err
}
// StartIssueStopWatch starts a stopwatch for an existing issue for a given
// repository
func (c *Client) StartIssueStopWatch(owner, repo string, index int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index), nil, nil)
return resp, err
}
// StopIssueStopWatch stops an existing stopwatch for an issue in a given
// repository
func (c *Client) StopIssueStopWatch(owner, repo string, index int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index), nil, nil)
return resp, err
}

View file

@ -0,0 +1,87 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"net/http"
)
// GetIssueSubscribers get list of users who subscribed on an issue
func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
subscribers := make([]*User, 0, 10)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions", owner, repo, index), nil, nil, &subscribers)
return subscribers, resp, err
}
// AddIssueSubscription Subscribe user to issue
func (c *Client) AddIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil {
return nil, err
}
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
if err != nil {
return resp, err
}
if status == http.StatusCreated {
return resp, nil
}
if status == http.StatusOK {
return resp, fmt.Errorf("already subscribed")
}
return resp, fmt.Errorf("unexpected Status: %d", status)
}
// DeleteIssueSubscription unsubscribe user from issue
func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil {
return nil, err
}
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
if err != nil {
return resp, err
}
if status == http.StatusCreated {
return resp, nil
}
if status == http.StatusOK {
return resp, fmt.Errorf("already unsubscribed")
}
return resp, fmt.Errorf("unexpected Status: %d", status)
}
// CheckIssueSubscription check if current user is subscribed to an issue
func (c *Client) CheckIssueSubscription(owner, repo string, index int64) (*WatchInfo, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
wi := new(WatchInfo)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/check", owner, repo, index), nil, nil, wi)
return wi, resp, err
}
// IssueSubscribe subscribe current user to an issue
func (c *Client) IssueSubscribe(owner, repo string, index int64) (*Response, error) {
u, _, err := c.GetMyUserInfo()
if err != nil {
return nil, err
}
return c.AddIssueSubscription(owner, repo, index, u.UserName)
}
// IssueUnSubscribe unsubscribe current user from an issue
func (c *Client) IssueUnSubscribe(owner, repo string, index int64) (*Response, error) {
u, _, err := c.GetMyUserInfo()
if err != nil {
return nil, err
}
return c.DeleteIssueSubscription(owner, repo, index, u.UserName)
}

View file

@ -0,0 +1,97 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
)
// IssueTemplate provides metadata and content on an issue template.
// There are two types of issue templates: .Markdown- and .Form-based.
type IssueTemplate struct {
Name string `json:"name"`
About string `json:"about"`
Filename string `json:"file_name"`
IssueTitle string `json:"title"`
IssueLabels []string `json:"labels"`
IssueRef string `json:"ref"`
// If non-nil, this is a form-based template
Form []IssueFormElement `json:"body"`
// Should only be used when .Form is nil.
MarkdownContent string `json:"content"`
}
// IssueFormElement describes a part of a IssueTemplate form
type IssueFormElement struct {
ID string `json:"id"`
Type IssueFormElementType `json:"type"`
Attributes IssueFormElementAttributes `json:"attributes"`
Validations IssueFormElementValidations `json:"validations"`
}
// IssueFormElementAttributes contains the combined set of attributes available on all element types.
type IssueFormElementAttributes struct {
// required for all element types.
// A brief description of the expected user input, which is also displayed in the form.
Label string `json:"label"`
// required for element types "dropdown", "checkboxes"
// for dropdown, contains the available options
Options []string `json:"options"`
// for element types "markdown", "textarea", "input"
// Text that is pre-filled in the input
Value string `json:"value"`
// for element types "textarea", "input", "dropdown", "checkboxes"
// A description of the text area to provide context or guidance, which is displayed in the form.
Description string `json:"description"`
// for element types "textarea", "input"
// A semi-opaque placeholder that renders in the text area when empty.
Placeholder string `json:"placeholder"`
// for element types "textarea"
// A language specifier. If set, the input is rendered as codeblock with syntax highlighting.
SyntaxHighlighting string `json:"render"`
// for element types "dropdown"
Multiple bool `json:"multiple"`
}
// IssueFormElementValidations contains the combined set of validations available on all element types.
type IssueFormElementValidations struct {
// for all element types
Required bool `json:"required"`
// for element types "input"
IsNumber bool `json:"is_number"`
// for element types "input"
Regex string `json:"regex"`
}
// IssueFormElementType is an enum
type IssueFormElementType string
const (
// IssueFormElementMarkdown is markdown rendered to the form for context, but omitted in the resulting issue
IssueFormElementMarkdown IssueFormElementType = "markdown"
// IssueFormElementTextarea is a multi line input
IssueFormElementTextarea IssueFormElementType = "textarea"
// IssueFormElementInput is a single line input
IssueFormElementInput IssueFormElementType = "input"
// IssueFormElementDropdown is a select form
IssueFormElementDropdown IssueFormElementType = "dropdown"
// IssueFormElementCheckboxes are a multi checkbox input
IssueFormElementCheckboxes IssueFormElementType = "checkboxes"
)
// GetIssueTemplates lists all issue templates of the repository
func (c *Client) GetIssueTemplates(owner, repo string) ([]*IssueTemplate, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
templates := new([]*IssueTemplate)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issue_templates", owner, repo), nil, nil, templates)
return *templates, resp, err
}
// IsForm tells if this template is a form instead of a markdown-based template.
func (t IssueTemplate) IsForm() bool {
return t.Form != nil
}

View file

@ -0,0 +1,142 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"time"
)
// TrackedTime worked time for an issue / pr
type TrackedTime struct {
ID int64 `json:"id"`
Created time.Time `json:"created"`
// Time in seconds
Time int64 `json:"time"`
// deprecated (only for backwards compatibility)
UserID int64 `json:"user_id"`
UserName string `json:"user_name"`
// deprecated (only for backwards compatibility)
IssueID int64 `json:"issue_id"`
Issue *Issue `json:"issue"`
}
// ListTrackedTimesOptions options for listing repository's tracked times
type ListTrackedTimesOptions struct {
ListOptions
Since time.Time
Before time.Time
// User filter is only used by ListRepoTrackedTimes !!!
User string
}
// QueryEncode turns options into querystring argument
func (opt *ListTrackedTimesOptions) QueryEncode() string {
query := opt.getURLQuery()
if !opt.Since.IsZero() {
query.Add("since", opt.Since.Format(time.RFC3339))
}
if !opt.Before.IsZero() {
query.Add("before", opt.Before.Format(time.RFC3339))
}
if len(opt.User) != 0 {
query.Add("user", opt.User)
}
return query.Encode()
}
// ListRepoTrackedTimes list tracked times of a repository
func (c *Client) ListRepoTrackedTimes(owner, repo string, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/times", owner, repo))
opt.setDefaults()
link.RawQuery = opt.QueryEncode()
times := make([]*TrackedTime, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &times)
return times, resp, err
}
// GetMyTrackedTimes list tracked times of the current user
func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, *Response, error) {
times := make([]*TrackedTime, 0, 10)
resp, err := c.getParsedResponse("GET", "/user/times", jsonHeader, nil, &times)
return times, resp, err
}
// AddTimeOption options for adding time to an issue
type AddTimeOption struct {
// time in seconds
Time int64 `json:"time"`
// optional
Created time.Time `json:"created"`
// optional
User string `json:"user_name"`
}
// Validate the AddTimeOption struct
func (opt AddTimeOption) Validate() error {
if opt.Time == 0 {
return fmt.Errorf("no time to add")
}
return nil
}
// AddTime adds time to issue with the given index
func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*TrackedTime, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
t := new(TrackedTime)
resp, err := c.getParsedResponse("POST",
fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index),
jsonHeader, bytes.NewReader(body), t)
return t, resp, err
}
// ListIssueTrackedTimes list tracked times of a single issue for a given repository
func (c *Client) ListIssueTrackedTimes(owner, repo string, index int64, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index))
opt.setDefaults()
link.RawQuery = opt.QueryEncode()
times := make([]*TrackedTime, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &times)
return times, resp, err
}
// ResetIssueTime reset tracked time of a single issue for a given repository
func (c *Client) ResetIssueTime(owner, repo string, index int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), jsonHeader, nil)
return resp, err
}
// DeleteTime delete a specific tracked time by id of a single issue for a given repository
func (c *Client) DeleteTime(owner, repo string, index, timeID int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times/%d", owner, repo, index, timeID), jsonHeader, nil)
return resp, err
}

View file

@ -0,0 +1,40 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"net/url"
)
// ListOptions options for using Gitea's API pagination
type ListOptions struct {
// Setting Page to -1 disables pagination on endpoints that support it.
// Page numbering starts at 1.
Page int
// The default value depends on the server config DEFAULT_PAGING_NUM
// The highest valid value depends on the server config MAX_RESPONSE_ITEMS
PageSize int
}
func (o ListOptions) getURLQuery() url.Values {
query := make(url.Values)
query.Add("page", fmt.Sprintf("%d", o.Page))
query.Add("limit", fmt.Sprintf("%d", o.PageSize))
return query
}
// setDefaults applies default pagination options.
// If .Page is set to -1, it will disable pagination.
// WARNING: This function is not idempotent, make sure to never call this method twice!
func (o *ListOptions) setDefaults() {
if o.Page < 0 {
o.Page, o.PageSize = 0, 0
return
} else if o.Page == 0 {
o.Page = 1
}
}

View file

@ -0,0 +1,257 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"net/url"
"time"
)
// NotificationThread expose Notification on API
type NotificationThread struct {
ID int64 `json:"id"`
Repository *Repository `json:"repository"`
Subject *NotificationSubject `json:"subject"`
Unread bool `json:"unread"`
Pinned bool `json:"pinned"`
UpdatedAt time.Time `json:"updated_at"`
URL string `json:"url"`
}
// NotificationSubject contains the notification subject (Issue/Pull/Commit)
type NotificationSubject struct {
Title string `json:"title"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
LatestCommentURL string `json:"latest_comment_url"`
LatestCommentHTMLURL string `json:"latest_comment_html_url"`
Type NotifySubjectType `json:"type"`
State NotifySubjectState `json:"state"`
}
// NotifyStatus notification status type
type NotifyStatus string
const (
// NotifyStatusUnread was not read
NotifyStatusUnread NotifyStatus = "unread"
// NotifyStatusRead was already read by user
NotifyStatusRead NotifyStatus = "read"
// NotifyStatusPinned notification is pinned by user
NotifyStatusPinned NotifyStatus = "pinned"
)
// NotifySubjectType represent type of notification subject
type NotifySubjectType string
const (
// NotifySubjectIssue an issue is subject of an notification
NotifySubjectIssue NotifySubjectType = "Issue"
// NotifySubjectPull an pull is subject of an notification
NotifySubjectPull NotifySubjectType = "Pull"
// NotifySubjectCommit an commit is subject of an notification
NotifySubjectCommit NotifySubjectType = "Commit"
// NotifySubjectRepository an repository is subject of an notification
NotifySubjectRepository NotifySubjectType = "Repository"
)
// NotifySubjectState reflect state of notification subject
type NotifySubjectState string
const (
// NotifySubjectOpen if subject is a pull/issue and is open at the moment
NotifySubjectOpen NotifySubjectState = "open"
// NotifySubjectClosed if subject is a pull/issue and is closed at the moment
NotifySubjectClosed NotifySubjectState = "closed"
// NotifySubjectMerged if subject is a pull and got merged
NotifySubjectMerged NotifySubjectState = "merged"
)
// ListNotificationOptions represents the filter options
type ListNotificationOptions struct {
ListOptions
Since time.Time
Before time.Time
Status []NotifyStatus
SubjectTypes []NotifySubjectType
}
// MarkNotificationOptions represents the filter & modify options
type MarkNotificationOptions struct {
LastReadAt time.Time
Status []NotifyStatus
ToStatus NotifyStatus
}
// QueryEncode encode options to url query
func (opt *ListNotificationOptions) QueryEncode() string {
query := opt.getURLQuery()
if !opt.Since.IsZero() {
query.Add("since", opt.Since.Format(time.RFC3339))
}
if !opt.Before.IsZero() {
query.Add("before", opt.Before.Format(time.RFC3339))
}
for _, s := range opt.Status {
query.Add("status-types", string(s))
}
for _, s := range opt.SubjectTypes {
query.Add("subject-type", string(s))
}
return query.Encode()
}
// Validate the CreateUserOption struct
func (opt ListNotificationOptions) Validate(c *Client) error {
if len(opt.Status) != 0 {
return c.checkServerVersionGreaterThanOrEqual(version1_12_3)
}
return nil
}
// QueryEncode encode options to url query
func (opt *MarkNotificationOptions) QueryEncode() string {
query := make(url.Values)
if !opt.LastReadAt.IsZero() {
query.Add("last_read_at", opt.LastReadAt.Format(time.RFC3339))
}
for _, s := range opt.Status {
query.Add("status-types", string(s))
}
if len(opt.ToStatus) != 0 {
query.Add("to-status", string(opt.ToStatus))
}
return query.Encode()
}
// Validate the CreateUserOption struct
func (opt MarkNotificationOptions) Validate(c *Client) error {
if len(opt.Status) != 0 || len(opt.ToStatus) != 0 {
return c.checkServerVersionGreaterThanOrEqual(version1_12_3)
}
return nil
}
// CheckNotifications list users's notification threads
func (c *Client) CheckNotifications() (int64, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return 0, nil, err
}
new := struct {
New int64 `json:"new"`
}{}
resp, err := c.getParsedResponse("GET", "/notifications/new", jsonHeader, nil, &new)
return new.New, resp, err
}
// GetNotification get notification thread by ID
func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
thread := new(NotificationThread)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/notifications/threads/%d", id), nil, nil, thread)
return thread, resp, err
}
// ReadNotification mark notification thread as read by ID
// It optionally takes a second argument if status has to be set other than 'read'
// The relevant notification will be returned as the first parameter when the Gitea server is 1.16.0 or higher.
func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*NotificationThread, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
link := fmt.Sprintf("/notifications/threads/%d", id)
if len(status) != 0 {
link += fmt.Sprintf("?to-status=%s", status[0])
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil {
thread := &NotificationThread{}
resp, err := c.getParsedResponse("PATCH", link, nil, nil, thread)
return thread, resp, err
}
_, resp, err := c.getResponse("PATCH", link, nil, nil)
return nil, resp, err
}
// ListNotifications list users's notification threads
func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
if err := opt.Validate(c); err != nil {
return nil, nil, err
}
link, _ := url.Parse("/notifications")
link.RawQuery = opt.QueryEncode()
threads := make([]*NotificationThread, 0, 10)
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads)
return threads, resp, err
}
// ReadNotifications mark notification threads as read
// The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher.
func (c *Client) ReadNotifications(opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
if err := opt.Validate(c); err != nil {
return nil, nil, err
}
link, _ := url.Parse("/notifications")
link.RawQuery = opt.QueryEncode()
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil {
threads := make([]*NotificationThread, 0, 10)
resp, err := c.getParsedResponse("PUT", link.String(), nil, nil, &threads)
return threads, resp, err
}
_, resp, err := c.getResponse("PUT", link.String(), nil, nil)
return nil, resp, err
}
// ListRepoNotifications list users's notification threads on a specific repo
func (c *Client) ListRepoNotifications(owner, repo string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
if err := opt.Validate(c); err != nil {
return nil, nil, err
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo))
link.RawQuery = opt.QueryEncode()
threads := make([]*NotificationThread, 0, 10)
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads)
return threads, resp, err
}
// ReadRepoNotifications mark notification threads as read on a specific repo
// The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher.
func (c *Client) ReadRepoNotifications(owner, repo string, opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
if err := opt.Validate(c); err != nil {
return nil, nil, err
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo))
link.RawQuery = opt.QueryEncode()
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil {
threads := make([]*NotificationThread, 0, 10)
resp, err := c.getParsedResponse("PUT", link.String(), nil, nil, &threads)
return threads, resp, err
}
_, resp, err := c.getResponse("PUT", link.String(), nil, nil)
return nil, resp, err
}

View file

@ -0,0 +1,93 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"time"
)
// Oauth2 represents an Oauth2 Application
type Oauth2 struct {
ID int64 `json:"id"`
Name string `json:"name"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectURIs []string `json:"redirect_uris"`
ConfidentialClient bool `json:"confidential_client"`
Created time.Time `json:"created"`
}
// ListOauth2Option for listing Oauth2 Applications
type ListOauth2Option struct {
ListOptions
}
// CreateOauth2Option required options for creating an Application
type CreateOauth2Option struct {
Name string `json:"name"`
ConfidentialClient bool `json:"confidential_client"`
RedirectURIs []string `json:"redirect_uris"`
}
// CreateOauth2 create an Oauth2 Application and returns a completed Oauth2 object.
func (c *Client) CreateOauth2(opt CreateOauth2Option) (*Oauth2, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
oauth := new(Oauth2)
resp, err := c.getParsedResponse("POST", "/user/applications/oauth2", jsonHeader, bytes.NewReader(body), oauth)
return oauth, resp, err
}
// UpdateOauth2 a specific Oauth2 Application by ID and return a completed Oauth2 object.
func (c *Client) UpdateOauth2(oauth2id int64, opt CreateOauth2Option) (*Oauth2, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
oauth := new(Oauth2)
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), jsonHeader, bytes.NewReader(body), oauth)
return oauth, resp, err
}
// GetOauth2 a specific Oauth2 Application by ID.
func (c *Client) GetOauth2(oauth2id int64) (*Oauth2, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
oauth2s := &Oauth2{}
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil, &oauth2s)
return oauth2s, resp, err
}
// ListOauth2 all of your Oauth2 Applications.
func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
opt.setDefaults()
oauth2s := make([]*Oauth2, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2?%s", opt.getURLQuery().Encode()), nil, nil, &oauth2s)
return oauth2s, resp, err
}
// DeleteOauth2 delete an Oauth2 application by ID
func (c *Client) DeleteOauth2(oauth2id int64) (*Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil)
return resp, err
}

163
forges/forgejo/sdk/org.go Normal file
View file

@ -0,0 +1,163 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// Organization represents an organization
type Organization struct {
ID int64 `json:"id"`
UserName string `json:"username"`
FullName string `json:"full_name"`
AvatarURL string `json:"avatar_url"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
Visibility string `json:"visibility"`
}
// VisibleType defines the visibility
type VisibleType string
const (
// VisibleTypePublic Visible for everyone
VisibleTypePublic VisibleType = "public"
// VisibleTypeLimited Visible for every connected user
VisibleTypeLimited VisibleType = "limited"
// VisibleTypePrivate Visible only for organization's members
VisibleTypePrivate VisibleType = "private"
)
// ListOrgsOptions options for listing organizations
type ListOrgsOptions struct {
ListOptions
}
// ListMyOrgs list all of current user's organizations
func (c *Client) ListMyOrgs(opt ListOrgsOptions) ([]*Organization, *Response, error) {
opt.setDefaults()
orgs := make([]*Organization, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs)
return orgs, resp, err
}
// ListOrgs list all organizations
func (c *Client) ListOrgs(opt ListOrgsOptions) ([]*Organization, *Response, error) {
opt.setDefaults()
orgs := make([]*Organization, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs)
return orgs, resp, err
}
// ListUserOrgs list all of some user's organizations
func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization, *Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, nil, err
}
opt.setDefaults()
orgs := make([]*Organization, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs?%s", user, opt.getURLQuery().Encode()), nil, nil, &orgs)
return orgs, resp, err
}
// GetOrg get one organization by name
func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) {
if err := escapeValidatePathSegments(&orgname); err != nil {
return nil, nil, err
}
org := new(Organization)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s", orgname), nil, nil, org)
return org, resp, err
}
// CreateOrgOption options for creating an organization
type CreateOrgOption struct {
Name string `json:"username"`
FullName string `json:"full_name"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
Visibility VisibleType `json:"visibility"`
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
}
// checkVisibilityOpt check if mode exist
func checkVisibilityOpt(v VisibleType) bool {
return v == VisibleTypePublic || v == VisibleTypeLimited || v == VisibleTypePrivate
}
// Validate the CreateOrgOption struct
func (opt CreateOrgOption) Validate() error {
if len(opt.Name) == 0 {
return fmt.Errorf("empty org name")
}
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
return fmt.Errorf("infalid bisibility option")
}
return nil
}
// CreateOrg creates an organization
func (c *Client) CreateOrg(opt CreateOrgOption) (*Organization, *Response, error) {
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
org := new(Organization)
resp, err := c.getParsedResponse("POST", "/orgs", jsonHeader, bytes.NewReader(body), org)
return org, resp, err
}
// EditOrgOption options for editing an organization
type EditOrgOption struct {
FullName string `json:"full_name"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
Visibility VisibleType `json:"visibility"`
}
// Validate the EditOrgOption struct
func (opt EditOrgOption) Validate() error {
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
return fmt.Errorf("infalid bisibility option")
}
return nil
}
// EditOrg modify one organization via options
func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) {
if err := escapeValidatePathSegments(&orgname); err != nil {
return nil, err
}
if err := opt.Validate(); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, bytes.NewReader(body))
return resp, err
}
// DeleteOrg deletes an organization
func (c *Client) DeleteOrg(orgname string) (*Response, error) {
if err := escapeValidatePathSegments(&orgname); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, nil)
return resp, err
}

View file

@ -0,0 +1,29 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"net/url"
)
// ListOrgMembershipOption list OrgMembership options
type ListOrgActionSecretOption struct {
ListOptions
}
// ListOrgMembership list an organization's members
func (c *Client) ListOrgActionSecret(org string, opt ListOrgActionSecretOption) ([]*Secret, *Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
opt.setDefaults()
secrets := make([]*Secret, 0, opt.PageSize)
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/actions/secrets", org))
link.RawQuery = opt.getURLQuery().Encode()
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &secrets)
return secrets, resp, err
}

View file

@ -0,0 +1,142 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"net/http"
"net/url"
)
// DeleteOrgMembership remove a member from an organization
func (c *Client) DeleteOrgMembership(org, user string) (*Response, error) {
if err := escapeValidatePathSegments(&org, &user); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil)
return resp, err
}
// ListOrgMembershipOption list OrgMembership options
type ListOrgMembershipOption struct {
ListOptions
}
// ListOrgMembership list an organization's members
func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
opt.setDefaults()
users := make([]*User, 0, opt.PageSize)
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/members", org))
link.RawQuery = opt.getURLQuery().Encode()
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
return users, resp, err
}
// ListPublicOrgMembership list an organization's members
func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
opt.setDefaults()
users := make([]*User, 0, opt.PageSize)
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/public_members", org))
link.RawQuery = opt.getURLQuery().Encode()
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
return users, resp, err
}
// CheckOrgMembership Check if a user is a member of an organization
func (c *Client) CheckOrgMembership(org, user string) (bool, *Response, error) {
if err := escapeValidatePathSegments(&org, &user); err != nil {
return false, nil, err
}
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil)
if err != nil {
return false, resp, err
}
switch status {
case http.StatusNoContent:
return true, resp, nil
case http.StatusNotFound:
return false, resp, nil
default:
return false, resp, fmt.Errorf("unexpected Status: %d", status)
}
}
// CheckPublicOrgMembership Check if a user is a member of an organization
func (c *Client) CheckPublicOrgMembership(org, user string) (bool, *Response, error) {
if err := escapeValidatePathSegments(&org, &user); err != nil {
return false, nil, err
}
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
if err != nil {
return false, resp, err
}
switch status {
case http.StatusNoContent:
return true, resp, nil
case http.StatusNotFound:
return false, resp, nil
default:
return false, resp, fmt.Errorf("unexpected Status: %d", status)
}
}
// SetPublicOrgMembership publicize/conceal a user's membership
func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Response, error) {
if err := escapeValidatePathSegments(&org, &user); err != nil {
return nil, err
}
var (
status int
err error
resp *Response
)
if visible {
status, resp, err = c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
} else {
status, resp, err = c.getStatusCode("DELETE", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
}
if err != nil {
return resp, err
}
switch status {
case http.StatusNoContent:
return resp, nil
case http.StatusNotFound:
return resp, fmt.Errorf("forbidden")
default:
return resp, fmt.Errorf("unexpected Status: %d", status)
}
}
// OrgPermissions represents the permissions for an user in an organization
type OrgPermissions struct {
CanCreateRepository bool `json:"can_create_repository"`
CanRead bool `json:"can_read"`
CanWrite bool `json:"can_write"`
IsAdmin bool `json:"is_admin"`
IsOwner bool `json:"is_owner"`
}
// GetOrgPermissions returns user permissions for specific organization.
func (c *Client) GetOrgPermissions(org, user string) (*OrgPermissions, *Response, error) {
if err := escapeValidatePathSegments(&org, &user); err != nil {
return nil, nil, err
}
perm := &OrgPermissions{}
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs/%s/permissions", user, org), jsonHeader, nil, &perm)
if err != nil {
return nil, resp, err
}
return perm, resp, nil
}

View file

@ -0,0 +1,283 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
)
// Team represents a team in an organization
type Team struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Organization *Organization `json:"organization"`
Permission AccessMode `json:"permission"`
CanCreateOrgRepo bool `json:"can_create_org_repo"`
IncludesAllRepositories bool `json:"includes_all_repositories"`
Units []RepoUnitType `json:"units"`
}
// RepoUnitType represent all unit types of a repo gitea currently offer
type RepoUnitType string
const (
// RepoUnitCode represent file view of a repository
RepoUnitCode RepoUnitType = "repo.code"
// RepoUnitIssues represent issues of a repository
RepoUnitIssues RepoUnitType = "repo.issues"
// RepoUnitPulls represent pulls of a repository
RepoUnitPulls RepoUnitType = "repo.pulls"
// RepoUnitExtIssues represent external issues of a repository
RepoUnitExtIssues RepoUnitType = "repo.ext_issues"
// RepoUnitWiki represent wiki of a repository
RepoUnitWiki RepoUnitType = "repo.wiki"
// RepoUnitExtWiki represent external wiki of a repository
RepoUnitExtWiki RepoUnitType = "repo.ext_wiki"
// RepoUnitReleases represent releases of a repository
RepoUnitReleases RepoUnitType = "repo.releases"
// RepoUnitProjects represent projects of a repository
RepoUnitProjects RepoUnitType = "repo.projects"
// RepoUnitPackages represents packages of a repository
RepoUnitPackages RepoUnitType = "repo.packages"
)
// ListTeamsOptions options for listing teams
type ListTeamsOptions struct {
ListOptions
}
// ListOrgTeams lists all teams of an organization
func (c *Client) ListOrgTeams(org string, opt ListTeamsOptions) ([]*Team, *Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
opt.setDefaults()
teams := make([]*Team, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams?%s", org, opt.getURLQuery().Encode()), nil, nil, &teams)
return teams, resp, err
}
// ListMyTeams lists all the teams of the current user
func (c *Client) ListMyTeams(opt *ListTeamsOptions) ([]*Team, *Response, error) {
opt.setDefaults()
teams := make([]*Team, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/teams?%s", opt.getURLQuery().Encode()), nil, nil, &teams)
return teams, resp, err
}
// GetTeam gets a team by ID
func (c *Client) GetTeam(id int64) (*Team, *Response, error) {
t := new(Team)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d", id), nil, nil, t)
return t, resp, err
}
// SearchTeamsOptions options for searching teams.
type SearchTeamsOptions struct {
ListOptions
Query string
IncludeDescription bool
}
func (o SearchTeamsOptions) getURLQuery() url.Values {
query := make(url.Values)
query.Add("page", fmt.Sprintf("%d", o.Page))
query.Add("limit", fmt.Sprintf("%d", o.PageSize))
query.Add("q", o.Query)
query.Add("include_desc", fmt.Sprintf("%t", o.IncludeDescription))
return query
}
// TeamSearchResults is the JSON struct that is returned from Team search API.
type TeamSearchResults struct {
OK bool `json:"ok"`
Error string `json:"error"`
Data []*Team `json:"data"`
}
// SearchOrgTeams search for teams in a org.
func (c *Client) SearchOrgTeams(org string, opt *SearchTeamsOptions) ([]*Team, *Response, error) {
responseBody := TeamSearchResults{}
opt.setDefaults()
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams/search?%s", org, opt.getURLQuery().Encode()), nil, nil, &responseBody)
if err != nil {
return nil, resp, err
}
if !responseBody.OK {
return nil, resp, fmt.Errorf("gitea error: %v", responseBody.Error)
}
return responseBody.Data, resp, err
}
// CreateTeamOption options for creating a team
type CreateTeamOption struct {
Name string `json:"name"`
Description string `json:"description"`
Permission AccessMode `json:"permission"`
CanCreateOrgRepo bool `json:"can_create_org_repo"`
IncludesAllRepositories bool `json:"includes_all_repositories"`
Units []RepoUnitType `json:"units"`
}
// Validate the CreateTeamOption struct
func (opt *CreateTeamOption) Validate() error {
if opt.Permission == AccessModeOwner {
opt.Permission = AccessModeAdmin
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
return fmt.Errorf("permission mode invalid")
}
if len(opt.Name) == 0 {
return fmt.Errorf("name required")
}
if len(opt.Name) > 30 {
return fmt.Errorf("name to long")
}
if len(opt.Description) > 255 {
return fmt.Errorf("description to long")
}
return nil
}
// CreateTeam creates a team for an organization
func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
if err := (&opt).Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
t := new(Team)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/teams", org), jsonHeader, bytes.NewReader(body), t)
return t, resp, err
}
// EditTeamOption options for editing a team
type EditTeamOption struct {
Name string `json:"name"`
Description *string `json:"description"`
Permission AccessMode `json:"permission"`
CanCreateOrgRepo *bool `json:"can_create_org_repo"`
IncludesAllRepositories *bool `json:"includes_all_repositories"`
Units []RepoUnitType `json:"units"`
}
// Validate the EditTeamOption struct
func (opt *EditTeamOption) Validate() error {
if opt.Permission == AccessModeOwner {
opt.Permission = AccessModeAdmin
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
return fmt.Errorf("permission mode invalid")
}
if len(opt.Name) == 0 {
return fmt.Errorf("name required")
}
if len(opt.Name) > 30 {
return fmt.Errorf("name to long")
}
if opt.Description != nil && len(*opt.Description) > 255 {
return fmt.Errorf("description to long")
}
return nil
}
// EditTeam edits a team of an organization
func (c *Client) EditTeam(id int64, opt EditTeamOption) (*Response, error) {
if err := (&opt).Validate(); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/teams/%d", id), jsonHeader, bytes.NewReader(body))
return resp, err
}
// DeleteTeam deletes a team of an organization
func (c *Client) DeleteTeam(id int64) (*Response, error) {
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d", id), nil, nil)
return resp, err
}
// ListTeamMembersOptions options for listing team's members
type ListTeamMembersOptions struct {
ListOptions
}
// ListTeamMembers lists all members of a team
func (c *Client) ListTeamMembers(id int64, opt ListTeamMembersOptions) ([]*User, *Response, error) {
opt.setDefaults()
members := make([]*User, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members?%s", id, opt.getURLQuery().Encode()), nil, nil, &members)
return members, resp, err
}
// GetTeamMember gets a member of a team
func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, nil, err
}
m := new(User)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil, m)
return m, resp, err
}
// AddTeamMember adds a member to a team
func (c *Client) AddTeamMember(id int64, user string) (*Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, err
}
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil)
return resp, err
}
// RemoveTeamMember removes a member from a team
func (c *Client) RemoveTeamMember(id int64, user string) (*Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil)
return resp, err
}
// ListTeamRepositoriesOptions options for listing team's repositories
type ListTeamRepositoriesOptions struct {
ListOptions
}
// ListTeamRepositories lists all repositories of a team
func (c *Client) ListTeamRepositories(id int64, opt ListTeamRepositoriesOptions) ([]*Repository, *Response, error) {
opt.setDefaults()
repos := make([]*Repository, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/repos?%s", id, opt.getURLQuery().Encode()), nil, nil, &repos)
return repos, resp, err
}
// AddTeamRepository adds a repository to a team
func (c *Client) AddTeamRepository(id int64, org, repo string) (*Response, error) {
if err := escapeValidatePathSegments(&org, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil)
return resp, err
}
// RemoveTeamRepository removes a repository from a team
func (c *Client) RemoveTeamRepository(id int64, org, repo string) (*Response, error) {
if err := escapeValidatePathSegments(&org, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil)
return resp, err
}

View file

@ -0,0 +1,93 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"time"
)
// Package represents a package
type Package struct {
// the package's id
ID int64 `json:"id"`
// the package's owner
Owner User `json:"owner"`
// the repo this package belongs to (if any)
Repository *string `json:"repository"`
// the package's creator
Creator User `json:"creator"`
// the type of package:
Type string `json:"type"`
// the name of the package
Name string `json:"name"`
// the version of the package
Version string `json:"version"`
// the date the package was uploaded
CreatedAt time.Time `json:"created_at"`
}
// PackageFile represents a file from a package
type PackageFile struct {
// the file's ID
ID int64 `json:"id"`
// the size of the file in bytes
Size int64 `json:"size"`
// the name of the file
Name string `json:"name"`
// the md5 hash of the file
MD5 string `json:"md5"`
// the sha1 hash of the file
SHA1 string `json:"sha1"`
// the sha256 hash of the file
SHA256 string `json:"sha256"`
// the sha512 hash of the file
SHA512 string `json:"sha512"`
}
// ListPackagesOptions options for listing packages
type ListPackagesOptions struct {
ListOptions
}
// ListPackages lists all the packages owned by a given owner (user, organisation)
func (c *Client) ListPackages(owner string, opt ListPackagesOptions) ([]*Package, *Response, error) {
if err := escapeValidatePathSegments(&owner); err != nil {
return nil, nil, err
}
opt.setDefaults()
packages := make([]*Package, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s?%s", owner, opt.getURLQuery().Encode()), nil, nil, &packages)
return packages, resp, err
}
// GetPackage gets the details of a specific package version
func (c *Client) GetPackage(owner, packageType, name, version string) (*Package, *Response, error) {
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
return nil, nil, err
}
foundPackage := new(Package)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil, foundPackage)
return foundPackage, resp, err
}
// DeletePackage deletes a specific package version
func (c *Client) DeletePackage(owner, packageType, name, version string) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil)
return resp, err
}
// ListPackageFiles lists the files within a package
func (c *Client) ListPackageFiles(owner, packageType, name, version string) ([]*PackageFile, *Response, error) {
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
return nil, nil, err
}
packageFiles := make([]*PackageFile, 0)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s/files", owner, packageType, name, version), nil, nil, &packageFiles)
return packageFiles, resp, err
}

383
forges/forgejo/sdk/pull.go Normal file
View file

@ -0,0 +1,383 @@
// Copyright 2016 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"strings"
"time"
)
// PRBranchInfo information about a branch
type PRBranchInfo struct {
Name string `json:"label"`
Ref string `json:"ref"`
Sha string `json:"sha"`
RepoID int64 `json:"repo_id"`
Repository *Repository `json:"repo"`
}
// PullRequest represents a pull request
type PullRequest struct {
ID int64 `json:"id"`
URL string `json:"url"`
Index int64 `json:"number"`
Poster *User `json:"user"`
Title string `json:"title"`
Body string `json:"body"`
Labels []*Label `json:"labels"`
Milestone *Milestone `json:"milestone"`
Assignee *User `json:"assignee"`
Assignees []*User `json:"assignees"`
State StateType `json:"state"`
IsLocked bool `json:"is_locked"`
Comments int `json:"comments"`
HTMLURL string `json:"html_url"`
DiffURL string `json:"diff_url"`
PatchURL string `json:"patch_url"`
Mergeable bool `json:"mergeable"`
HasMerged bool `json:"merged"`
Merged *time.Time `json:"merged_at"`
MergedCommitID *string `json:"merge_commit_sha"`
MergedBy *User `json:"merged_by"`
AllowMaintainerEdit bool `json:"allow_maintainer_edit"`
Base *PRBranchInfo `json:"base"`
Head *PRBranchInfo `json:"head"`
MergeBase string `json:"merge_base"`
Deadline *time.Time `json:"due_date"`
Created *time.Time `json:"created_at"`
Updated *time.Time `json:"updated_at"`
Closed *time.Time `json:"closed_at"`
}
// ChangedFile is a changed file in a diff
type ChangedFile struct {
Filename string `json:"filename"`
PreviousFilename string `json:"previous_filename"`
Status string `json:"status"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
Changes int `json:"changes"`
HTMLURL string `json:"html_url"`
ContentsURL string `json:"contents_url"`
RawURL string `json:"raw_url"`
}
// ListPullRequestsOptions options for listing pull requests
type ListPullRequestsOptions struct {
ListOptions
State StateType `json:"state"`
// oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority
Sort string
Milestone int64
}
// MergeStyle is used specify how a pull is merged
type MergeStyle string
const (
// MergeStyleMerge merge pull as usual
MergeStyleMerge MergeStyle = "merge"
// MergeStyleRebase rebase pull
MergeStyleRebase MergeStyle = "rebase"
// MergeStyleRebaseMerge rebase and merge pull
MergeStyleRebaseMerge MergeStyle = "rebase-merge"
// MergeStyleSquash squash and merge pull
MergeStyleSquash MergeStyle = "squash"
)
// QueryEncode turns options into querystring argument
func (opt *ListPullRequestsOptions) QueryEncode() string {
query := opt.getURLQuery()
if len(opt.State) > 0 {
query.Add("state", string(opt.State))
}
if len(opt.Sort) > 0 {
query.Add("sort", opt.Sort)
}
if opt.Milestone > 0 {
query.Add("milestone", fmt.Sprintf("%d", opt.Milestone))
}
return query.Encode()
}
// ListRepoPullRequests list PRs of one repository
func (c *Client) ListRepoPullRequests(owner, repo string, opt ListPullRequestsOptions) ([]*PullRequest, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
prs := make([]*PullRequest, 0, opt.PageSize)
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls", owner, repo))
link.RawQuery = opt.QueryEncode()
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &prs)
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
for i := range prs {
if err := fixPullHeadSha(c, prs[i]); err != nil {
return prs, resp, err
}
}
}
return prs, resp, err
}
// GetPullRequest get information of one PR
func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
pr := new(PullRequest)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), nil, nil, pr)
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
if err := fixPullHeadSha(c, pr); err != nil {
return pr, resp, err
}
}
return pr, resp, err
}
// CreatePullRequestOption options when creating a pull request
type CreatePullRequestOption struct {
Head string `json:"head"`
Base string `json:"base"`
Title string `json:"title"`
Body string `json:"body"`
Assignee string `json:"assignee"`
Assignees []string `json:"assignees"`
Milestone int64 `json:"milestone"`
Labels []int64 `json:"labels"`
Deadline *time.Time `json:"due_date"`
}
// CreatePullRequest create pull request with options
func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOption) (*PullRequest, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
pr := new(PullRequest)
resp, err := c.getParsedResponse("POST",
fmt.Sprintf("/repos/%s/%s/pulls", owner, repo),
jsonHeader, bytes.NewReader(body), pr)
return pr, resp, err
}
// EditPullRequestOption options when modify pull request
type EditPullRequestOption struct {
Title string `json:"title"`
Body string `json:"body"`
Base string `json:"base"`
Assignee string `json:"assignee"`
Assignees []string `json:"assignees"`
Milestone int64 `json:"milestone"`
Labels []int64 `json:"labels"`
State *StateType `json:"state"`
Deadline *time.Time `json:"due_date"`
RemoveDeadline *bool `json:"unset_due_date"`
AllowMaintainerEdit *bool `json:"allow_maintainer_edit"`
}
// Validate the EditPullRequestOption struct
func (opt EditPullRequestOption) Validate(c *Client) error {
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 {
return fmt.Errorf("title is empty")
}
if len(opt.Base) != 0 {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return fmt.Errorf("can not change base gitea to old")
}
}
return nil
}
// EditPullRequest modify pull request with PR id and options
func (c *Client) EditPullRequest(owner, repo string, index int64, opt EditPullRequestOption) (*PullRequest, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(c); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
pr := new(PullRequest)
resp, err := c.getParsedResponse("PATCH",
fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index),
jsonHeader, bytes.NewReader(body), pr)
return pr, resp, err
}
// MergePullRequestOption options when merging a pull request
type MergePullRequestOption struct {
Style MergeStyle `json:"Do"`
MergeCommitID string `json:"MergeCommitID"`
Title string `json:"MergeTitleField"`
Message string `json:"MergeMessageField"`
DeleteBranchAfterMerge bool `json:"delete_branch_after_merge"`
ForceMerge bool `json:"force_merge"`
HeadCommitId string `json:"head_commit_id"`
MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed"`
}
// Validate the MergePullRequestOption struct
func (opt MergePullRequestOption) Validate(c *Client) error {
if opt.Style == MergeStyleSquash {
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_5); err != nil {
return err
}
}
return nil
}
// MergePullRequest merge a PR to repository by PR id
func (c *Client) MergePullRequest(owner, repo string, index int64, opt MergePullRequestOption) (bool, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return false, nil, err
}
if err := opt.Validate(c); err != nil {
return false, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return false, nil, err
}
status, resp, err := c.getStatusCode("POST", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), jsonHeader, bytes.NewReader(body))
if err != nil {
return false, resp, err
}
return status == 200, resp, nil
}
// IsPullRequestMerged test if one PR is merged to one repository
func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return false, nil, err
}
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil)
if err != nil {
return false, resp, err
}
return status == 204, resp, nil
}
// PullRequestDiffOptions options for GET /repos/<owner>/<repo>/pulls/<idx>.[diff|patch]
type PullRequestDiffOptions struct {
// Include binary file changes when requesting a .diff
Binary bool
}
// QueryEncode converts the options to a query string
func (o PullRequestDiffOptions) QueryEncode() string {
query := make(url.Values)
query.Add("binary", fmt.Sprintf("%v", o.Binary))
return query.Encode()
}
type pullRequestDiffType string
const (
pullRequestDiffTypeDiff pullRequestDiffType = "diff"
pullRequestDiffTypePatch pullRequestDiffType = "patch"
)
// getPullRequestDiffOrPatch gets the patch or diff file as bytes for a PR
func (c *Client) getPullRequestDiffOrPatch(owner, repo string, kind pullRequestDiffType, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
r, _, err2 := c.GetRepo(owner, repo)
if err2 != nil {
return nil, nil, err
}
if r.Private {
return nil, nil, err
}
url := fmt.Sprintf("/%s/%s/pulls/%d.%s?%s", owner, repo, index, kind, opts.QueryEncode())
return c.getWebResponse("GET", url, nil)
}
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil, nil)
}
// GetPullRequestPatch gets the git patchset of a PR
func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *Response, error) {
return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypePatch, index, PullRequestDiffOptions{})
}
// GetPullRequestDiff gets the diff of a PR. For Gitea >= 1.16, you must set includeBinary to get an applicable diff
func (c *Client) GetPullRequestDiff(owner, repo string, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) {
return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypeDiff, index, opts)
}
// ListPullRequestCommitsOptions options for listing pull requests
type ListPullRequestCommitsOptions struct {
ListOptions
}
// ListPullRequestCommits list commits for a pull request
func (c *Client) ListPullRequestCommits(owner, repo string, index int64, opt ListPullRequestCommitsOptions) ([]*Commit, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/commits", owner, repo, index))
opt.setDefaults()
commits := make([]*Commit, 0, opt.PageSize)
link.RawQuery = opt.getURLQuery().Encode()
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits)
return commits, resp, err
}
// fixPullHeadSha is a workaround for https://github.com/go-gitea/gitea/issues/12675
// When no head sha is available, this is because the branch got deleted in the base repo.
// pr.Head.Ref points in this case not to the head repo branch name, but the base repo ref,
// which stays available to resolve the commit sha. This is fixed for gitea >= 1.14.0
func fixPullHeadSha(client *Client, pr *PullRequest) error {
if pr.Base != nil && pr.Base.Repository != nil && pr.Base.Repository.Owner != nil &&
pr.Head != nil && pr.Head.Ref != "" && pr.Head.Sha == "" {
owner := pr.Base.Repository.Owner.UserName
repo := pr.Base.Repository.Name
refs, _, err := client.GetRepoRefs(owner, repo, pr.Head.Ref)
if err != nil {
return err
} else if len(refs) == 0 {
return fmt.Errorf("unable to resolve PR ref '%s'", pr.Head.Ref)
}
pr.Head.Sha = refs[0].Object.SHA
}
return nil
}
// ListPullRequestFilesOptions options for listing pull request files
type ListPullRequestFilesOptions struct {
ListOptions
}
// ListPullRequestFiles list changed files for a pull request
func (c *Client) ListPullRequestFiles(owner, repo string, index int64, opt ListPullRequestFilesOptions) ([]*ChangedFile, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/files", owner, repo, index))
opt.setDefaults()
files := make([]*ChangedFile, 0, opt.PageSize)
link.RawQuery = opt.getURLQuery().Encode()
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &files)
return files, resp, err
}

View file

@ -0,0 +1,368 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"strings"
"time"
)
// ReviewStateType review state type
type ReviewStateType string
const (
// ReviewStateApproved pr is approved
ReviewStateApproved ReviewStateType = "APPROVED"
// ReviewStatePending pr state is pending
ReviewStatePending ReviewStateType = "PENDING"
// ReviewStateComment is a comment review
ReviewStateComment ReviewStateType = "COMMENT"
// ReviewStateRequestChanges changes for pr are requested
ReviewStateRequestChanges ReviewStateType = "REQUEST_CHANGES"
// ReviewStateRequestReview review is requested from user
ReviewStateRequestReview ReviewStateType = "REQUEST_REVIEW"
// ReviewStateUnknown state of pr is unknown
ReviewStateUnknown ReviewStateType = ""
)
// PullReview represents a pull request review
type PullReview struct {
ID int64 `json:"id"`
Reviewer *User `json:"user"`
ReviewerTeam *Team `json:"team"`
State ReviewStateType `json:"state"`
Body string `json:"body"`
CommitID string `json:"commit_id"`
// Stale indicates if the pull has changed since the review
Stale bool `json:"stale"`
// Official indicates if the review counts towards the required approval limit, if PR base is a protected branch
Official bool `json:"official"`
Dismissed bool `json:"dismissed"`
CodeCommentsCount int `json:"comments_count"`
Submitted time.Time `json:"submitted_at"`
HTMLURL string `json:"html_url"`
HTMLPullURL string `json:"pull_request_url"`
}
// PullReviewComment represents a comment on a pull request review
type PullReviewComment struct {
ID int64 `json:"id"`
Body string `json:"body"`
Reviewer *User `json:"user"`
ReviewID int64 `json:"pull_request_review_id"`
Resolver *User `json:"resolver"`
Created time.Time `json:"created_at"`
Updated time.Time `json:"updated_at"`
Path string `json:"path"`
CommitID string `json:"commit_id"`
OrigCommitID string `json:"original_commit_id"`
DiffHunk string `json:"diff_hunk"`
LineNum uint64 `json:"position"`
OldLineNum uint64 `json:"original_position"`
HTMLURL string `json:"html_url"`
HTMLPullURL string `json:"pull_request_url"`
}
// CreatePullReviewOptions are options to create a pull review
type CreatePullReviewOptions struct {
State ReviewStateType `json:"event"`
Body string `json:"body"`
CommitID string `json:"commit_id"`
Comments []CreatePullReviewComment `json:"comments"`
}
// CreatePullReviewComment represent a review comment for creation api
type CreatePullReviewComment struct {
// the tree path
Path string `json:"path"`
Body string `json:"body"`
// if comment to old file line or 0
OldLineNum uint64 `json:"old_position"`
// if comment to new file line or 0
LineNum uint64 `json:"new_position"`
}
// SubmitPullReviewOptions are options to submit a pending pull review
type SubmitPullReviewOptions struct {
State ReviewStateType `json:"event"`
Body string `json:"body"`
}
// DismissPullReviewOptions are options to dismiss a pull review
type DismissPullReviewOptions struct {
Message string `json:"message"`
}
// PullReviewRequestOptions are options to add or remove pull review requests
type PullReviewRequestOptions struct {
Reviewers []string `json:"reviewers"`
TeamReviewers []string `json:"team_reviewers"`
}
// ListPullReviewsOptions options for listing PullReviews
type ListPullReviewsOptions struct {
ListOptions
}
// Validate the CreatePullReviewOptions struct
func (opt CreatePullReviewOptions) Validate() error {
if opt.State != ReviewStateApproved && len(opt.Comments) == 0 && len(strings.TrimSpace(opt.Body)) == 0 {
return fmt.Errorf("body is empty")
}
for i := range opt.Comments {
if err := opt.Comments[i].Validate(); err != nil {
return err
}
}
return nil
}
// Validate the SubmitPullReviewOptions struct
func (opt SubmitPullReviewOptions) Validate() error {
if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 {
return fmt.Errorf("body is empty")
}
return nil
}
// Validate the CreatePullReviewComment struct
func (opt CreatePullReviewComment) Validate() error {
if len(strings.TrimSpace(opt.Body)) == 0 {
return fmt.Errorf("body is empty")
}
if opt.LineNum != 0 && opt.OldLineNum != 0 {
return fmt.Errorf("old and new line num are set, cant identify the code comment position")
}
return nil
}
// ListPullReviews lists all reviews of a pull request
func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullReviewsOptions) ([]*PullReview, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
opt.setDefaults()
rs := make([]*PullReview, 0, opt.PageSize)
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index))
link.RawQuery = opt.ListOptions.getURLQuery().Encode()
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rs)
return rs, resp, err
}
// GetPullReview gets a specific review of a pull request
func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
r := new(PullReview)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil, &r)
return r, resp, err
}
// ListPullReviewComments lists all comments of a pull request review
func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]*PullReviewComment, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
rcl := make([]*PullReviewComment, 0, 4)
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/comments", owner, repo, index, id))
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rcl)
return rcl, resp, err
}
// GetPullReviewComment get a comment from a pull request review
func (c *Client) GetPullReviewComment(owner, repo string, index, id, comment int64) (*PullReviewComment, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
rc := new(PullReviewComment)
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/comments/%d", owner, repo, index, id, comment))
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, rc)
return rc, resp, err
}
// DeletePullReviewComment delete a comment from a pull request review
func (c *Client) DeletePullReviewComment(owner, repo string, index, id, comment int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/comments/%d", owner, repo, index, id, comment))
_, resp, err := c.getResponse("DELETE", link.String(), jsonHeader, nil)
return resp, err
}
// CreatePullReviewComment create a review comment for an existing review
func (c *Client) CreatePullReviewComment(owner, repo string, index, id int64, opt CreatePullReviewComment) (*PullReviewComment, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
r := new(PullReviewComment)
resp, err := c.getParsedResponse("POST",
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/comments", owner, repo, index, id),
jsonHeader, bytes.NewReader(body), r)
return r, resp, err
}
// DeletePullReview delete a specific review from a pull request
func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil)
return resp, err
}
// CreatePullReview create a review to an pull request
func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePullReviewOptions) (*PullReview, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
r := new(PullReview)
resp, err := c.getParsedResponse("POST",
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index),
jsonHeader, bytes.NewReader(body), r)
return r, resp, err
}
// SubmitPullReview submit a pending review to an pull request
func (c *Client) SubmitPullReview(owner, repo string, index, id int64, opt SubmitPullReviewOptions) (*PullReview, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
r := new(PullReview)
resp, err := c.getParsedResponse("POST",
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id),
jsonHeader, bytes.NewReader(body), r)
return r, resp, err
}
// CreateReviewRequests create review requests to an pull request
func (c *Client) CreateReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("POST",
fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index),
jsonHeader, bytes.NewReader(body))
return resp, err
}
// DeleteReviewRequests delete review requests to an pull request
func (c *Client) DeleteReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE",
fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index),
jsonHeader, bytes.NewReader(body))
return resp, err
}
// DismissPullReview dismiss a review for a pull request
func (c *Client) DismissPullReview(owner, repo string, index, id int64, opt DismissPullReviewOptions) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("POST",
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/dismissals", owner, repo, index, id),
jsonHeader, bytes.NewReader(body))
return resp, err
}
// UnDismissPullReview cancel to dismiss a review for a pull request
func (c *Client) UnDismissPullReview(owner, repo string, index, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
return nil, err
}
_, resp, err := c.getResponse("POST",
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/undismissals", owner, repo, index, id),
jsonHeader, nil)
return resp, err
}

View file

@ -0,0 +1,202 @@
// Copyright 2016 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)
// Release represents a repository release
type Release struct {
ID int64 `json:"id"`
TagName string `json:"tag_name"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
TarURL string `json:"tarball_url"`
ZipURL string `json:"zipball_url"`
IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"`
CreatedAt time.Time `json:"created_at"`
PublishedAt time.Time `json:"published_at"`
Publisher *User `json:"author"`
Attachments []*Attachment `json:"assets"`
}
// ListReleasesOptions options for listing repository's releases
type ListReleasesOptions struct {
ListOptions
IsDraft *bool
IsPreRelease *bool
}
// QueryEncode turns options into querystring argument
func (opt *ListReleasesOptions) QueryEncode() string {
query := opt.getURLQuery()
if opt.IsDraft != nil {
query.Add("draft", fmt.Sprintf("%t", *opt.IsDraft))
}
if opt.IsPreRelease != nil {
query.Add("pre-release", fmt.Sprintf("%t", *opt.IsPreRelease))
}
return query.Encode()
}
// ListReleases list releases of a repository
func (c *Client) ListReleases(owner, repo string, opt ListReleasesOptions) ([]*Release, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
releases := make([]*Release, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET",
fmt.Sprintf("/repos/%s/%s/releases?%s", owner, repo, opt.QueryEncode()),
nil, nil, &releases)
return releases, resp, err
}
// GetRelease get a release of a repository by id
func (c *Client) GetRelease(owner, repo string, id int64) (*Release, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
r := new(Release)
resp, err := c.getParsedResponse("GET",
fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id),
jsonHeader, nil, &r)
return r, resp, err
}
// GetReleaseByTag get a release of a repository by tag
func (c *Client) GetReleaseByTag(owner, repo, tag string) (*Release, *Response, error) {
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
return c.fallbackGetReleaseByTag(owner, repo, tag)
}
if err := escapeValidatePathSegments(&owner, &repo, &tag); err != nil {
return nil, nil, err
}
r := new(Release)
resp, err := c.getParsedResponse("GET",
fmt.Sprintf("/repos/%s/%s/releases/tags/%s", owner, repo, tag),
nil, nil, &r)
return r, resp, err
}
// CreateReleaseOption options when creating a release
type CreateReleaseOption struct {
TagName string `json:"tag_name"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"`
}
// Validate the CreateReleaseOption struct
func (opt CreateReleaseOption) Validate() error {
if len(strings.TrimSpace(opt.Title)) == 0 {
return fmt.Errorf("title is empty")
}
return nil
}
// CreateRelease create a release
func (c *Client) CreateRelease(owner, repo string, opt CreateReleaseOption) (*Release, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(opt)
if err != nil {
return nil, nil, err
}
r := new(Release)
resp, err := c.getParsedResponse("POST",
fmt.Sprintf("/repos/%s/%s/releases", owner, repo),
jsonHeader, bytes.NewReader(body), r)
return r, resp, err
}
// EditReleaseOption options when editing a release
type EditReleaseOption struct {
TagName string `json:"tag_name"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
IsDraft *bool `json:"draft"`
IsPrerelease *bool `json:"prerelease"`
}
// EditRelease edit a release
func (c *Client) EditRelease(owner, repo string, id int64, form EditReleaseOption) (*Release, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
body, err := json.Marshal(form)
if err != nil {
return nil, nil, err
}
r := new(Release)
resp, err := c.getParsedResponse("PATCH",
fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id),
jsonHeader, bytes.NewReader(body), r)
return r, resp, err
}
// DeleteRelease delete a release from a repository, keeping its tag
func (c *Client) DeleteRelease(user, repo string, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE",
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
nil, nil)
return resp, err
}
// DeleteReleaseByTag deletes a release frm a repository by tag
func (c *Client) DeleteReleaseByTag(user, repo, tag string) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
return nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE",
fmt.Sprintf("/repos/%s/%s/releases/tags/%s", user, repo, tag),
nil, nil)
return resp, err
}
// fallbackGetReleaseByTag is fallback for old gitea installations ( < 1.13.0 )
func (c *Client) fallbackGetReleaseByTag(owner, repo, tag string) (*Release, *Response, error) {
for i := 1; ; i++ {
rl, resp, err := c.ListReleases(owner, repo, ListReleasesOptions{ListOptions: ListOptions{Page: i}})
if err != nil {
return nil, resp, err
}
if len(rl) == 0 {
return nil,
newResponse(&http.Response{StatusCode: 404}),
fmt.Errorf("release with tag '%s' not found", tag)
}
for _, r := range rl {
if r.TagName == tag {
return r, resp, nil
}
}
}
}

538
forges/forgejo/sdk/repo.go Normal file
View file

@ -0,0 +1,538 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/url"
"strings"
"time"
)
// Permission represents a set of permissions
type Permission struct {
Admin bool `json:"admin"`
Push bool `json:"push"`
Pull bool `json:"pull"`
}
// InternalTracker represents settings for internal tracker
type InternalTracker struct {
// Enable time tracking (Built-in issue tracker)
EnableTimeTracker bool `json:"enable_time_tracker"`
// Let only contributors track time (Built-in issue tracker)
AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"`
// Enable dependencies for issues and pull requests (Built-in issue tracker)
EnableIssueDependencies bool `json:"enable_issue_dependencies"`
}
// ExternalTracker represents settings for external tracker
type ExternalTracker struct {
// URL of external issue tracker.
ExternalTrackerURL string `json:"external_tracker_url"`
// External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index.
ExternalTrackerFormat string `json:"external_tracker_format"`
// External Issue Tracker Number Format, either `numeric` or `alphanumeric`
ExternalTrackerStyle string `json:"external_tracker_style"`
}
// ExternalWiki represents setting for external wiki
type ExternalWiki struct {
// URL of external wiki.
ExternalWikiURL string `json:"external_wiki_url"`
}
// Repository represents a repository
type Repository struct {
ID int64 `json:"id"`
Owner *User `json:"owner"`
Name string `json:"name"`
FullName string `json:"full_name"`
Description string `json:"description"`
Empty bool `json:"empty"`
Private bool `json:"private"`
Fork bool `json:"fork"`
Template bool `json:"template"`
Parent *Repository `json:"parent"`
Mirror bool `json:"mirror"`
Size int `json:"size"`
HTMLURL string `json:"html_url"`
SSHURL string `json:"ssh_url"`
CloneURL string `json:"clone_url"`
OriginalURL string `json:"original_url"`
Website string `json:"website"`
Stars int `json:"stars_count"`
Forks int `json:"forks_count"`
Watchers int `json:"watchers_count"`
OpenIssues int `json:"open_issues_count"`
OpenPulls int `json:"open_pr_counter"`
Releases int `json:"release_counter"`
DefaultBranch string `json:"default_branch"`
Archived bool `json:"archived"`
Created time.Time `json:"created_at"`
Updated time.Time `json:"updated_at"`
Permissions *Permission `json:"permissions,omitempty"`
HasIssues bool `json:"has_issues"`
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
HasWiki bool `json:"has_wiki"`
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
HasPullRequests bool `json:"has_pull_requests"`
HasReleases bool `json:"has_releases"`
HasProjects bool `json:"has_projects"`
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"`
AllowMerge bool `json:"allow_merge_commits"`
AllowRebase bool `json:"allow_rebase"`
AllowRebaseMerge bool `json:"allow_rebase_explicit"`
AllowSquash bool `json:"allow_squash_merge"`
AvatarURL string `json:"avatar_url"`
Internal bool `json:"internal"`
MirrorInterval string `json:"mirror_interval"`
MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
DefaultMergeStyle MergeStyle `json:"default_merge_style"`
}
// RepoType represent repo type
type RepoType string
const (
// RepoTypeNone dont specify a type
RepoTypeNone RepoType = ""
// RepoTypeSource is the default repo type
RepoTypeSource RepoType = "source"
// RepoTypeFork is a repo witch was forked from an other one
RepoTypeFork RepoType = "fork"
// RepoTypeMirror represents an mirror repo
RepoTypeMirror RepoType = "mirror"
)
// TrustModel represent how git signatures are handled in a repository
type TrustModel string
const (
// TrustModelDefault use TM set by global config
TrustModelDefault TrustModel = "default"
// TrustModelCollaborator gpg signature has to be owned by a repo collaborator
TrustModelCollaborator TrustModel = "collaborator"
// TrustModelCommitter gpg signature has to match committer
TrustModelCommitter TrustModel = "committer"
// TrustModelCollaboratorCommitter gpg signature has to match committer and owned by a repo collaborator
TrustModelCollaboratorCommitter TrustModel = "collaboratorcommitter"
)
// ListReposOptions options for listing repositories
type ListReposOptions struct {
ListOptions
}
// ListMyRepos lists all repositories for the authenticated user that has access to.
func (c *Client) ListMyRepos(opt ListReposOptions) ([]*Repository, *Response, error) {
opt.setDefaults()
repos := make([]*Repository, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/repos?%s", opt.getURLQuery().Encode()), nil, nil, &repos)
return repos, resp, err
}
// ListUserRepos list all repositories of one user by user's name
func (c *Client) ListUserRepos(user string, opt ListReposOptions) ([]*Repository, *Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, nil, err
}
opt.setDefaults()
repos := make([]*Repository, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/repos?%s", user, opt.getURLQuery().Encode()), nil, nil, &repos)
return repos, resp, err
}
// ListOrgReposOptions options for a organization's repositories
type ListOrgReposOptions struct {
ListOptions
}
// ListOrgRepos list all repositories of one organization by organization's name
func (c *Client) ListOrgRepos(org string, opt ListOrgReposOptions) ([]*Repository, *Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
opt.setDefaults()
repos := make([]*Repository, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/repos?%s", org, opt.getURLQuery().Encode()), nil, nil, &repos)
return repos, resp, err
}
// SearchRepoOptions options for searching repositories
type SearchRepoOptions struct {
ListOptions
// The keyword to query
Keyword string
// Limit search to repositories with keyword as topic
KeywordIsTopic bool
// Include search of keyword within repository description
KeywordInDescription bool
/*
User Filter
*/
// Repo Owner
OwnerID int64
// Stared By UserID
StarredByUserID int64
/*
Repo Attributes
*/
// pubic, private or all repositories (defaults to all)
IsPrivate *bool
// archived, non-archived or all repositories (defaults to all)
IsArchived *bool
// Exclude template repos from search
ExcludeTemplate bool
// Filter by "fork", "source", "mirror"
Type RepoType
/*
Sort Filters
*/
// sort repos by attribute. Supported values are "alpha", "created", "updated", "size", and "id". Default is "alpha"
Sort string
// sort order, either "asc" (ascending) or "desc" (descending). Default is "asc", ignored if "sort" is not specified.
Order string
// Repo owner to prioritize in the results
PrioritizedByOwnerID int64
/*
Cover EdgeCases
*/
// if set all other options are ignored and this string is used as query
RawQuery string
}
// QueryEncode turns options into querystring argument
func (opt *SearchRepoOptions) QueryEncode() string {
query := opt.getURLQuery()
if opt.Keyword != "" {
query.Add("q", opt.Keyword)
}
if opt.KeywordIsTopic {
query.Add("topic", "true")
}
if opt.KeywordInDescription {
query.Add("includeDesc", "true")
}
// User Filter
if opt.OwnerID > 0 {
query.Add("uid", fmt.Sprintf("%d", opt.OwnerID))
query.Add("exclusive", "true")
}
if opt.StarredByUserID > 0 {
query.Add("starredBy", fmt.Sprintf("%d", opt.StarredByUserID))
}
// Repo Attributes
if opt.IsPrivate != nil {
query.Add("is_private", fmt.Sprintf("%v", opt.IsPrivate))
}
if opt.IsArchived != nil {
query.Add("archived", fmt.Sprintf("%v", opt.IsArchived))
}
if opt.ExcludeTemplate {
query.Add("template", "false")
}
if len(opt.Type) != 0 {
query.Add("mode", string(opt.Type))
}
// Sort Filters
if opt.Sort != "" {
query.Add("sort", opt.Sort)
}
if opt.PrioritizedByOwnerID > 0 {
query.Add("priority_owner_id", fmt.Sprintf("%d", opt.PrioritizedByOwnerID))
}
if opt.Order != "" {
query.Add("order", opt.Order)
}
return query.Encode()
}
type searchRepoResponse struct {
Repos []*Repository `json:"data"`
}
// SearchRepos searches for repositories matching the given filters
func (c *Client) SearchRepos(opt SearchRepoOptions) ([]*Repository, *Response, error) {
opt.setDefaults()
repos := new(searchRepoResponse)
link, _ := url.Parse("/repos/search")
if len(opt.RawQuery) != 0 {
link.RawQuery = opt.RawQuery
} else {
link.RawQuery = opt.QueryEncode()
// IsPrivate only works on gitea >= 1.12.0
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil && opt.IsPrivate != nil {
if *opt.IsPrivate {
// private repos only not supported on gitea <= 1.11.x
return nil, nil, err
}
newQuery := link.Query()
newQuery.Add("private", "false")
link.RawQuery = newQuery.Encode()
}
}
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &repos)
return repos.Repos, resp, err
}
// CreateRepoOption options when creating repository
type CreateRepoOption struct {
// Name of the repository to create
Name string `json:"name"`
// Description of the repository to create
Description string `json:"description"`
// Whether the repository is private
Private bool `json:"private"`
// Issue Label set to use
IssueLabels string `json:"issue_labels"`
// Whether the repository should be auto-intialized?
AutoInit bool `json:"auto_init"`
// Whether the repository is template
Template bool `json:"template"`
// Gitignores to use
Gitignores string `json:"gitignores"`
// License to use
License string `json:"license"`
// Readme of the repository to create
Readme string `json:"readme"`
// DefaultBranch of the repository (used when initializes and in template)
DefaultBranch string `json:"default_branch"`
// TrustModel of the repository
TrustModel TrustModel `json:"trust_model"`
}
// Validate the CreateRepoOption struct
func (opt CreateRepoOption) Validate(c *Client) error {
if len(strings.TrimSpace(opt.Name)) == 0 {
return fmt.Errorf("name is empty")
}
if len(opt.Name) > 100 {
return fmt.Errorf("name has more than 100 chars")
}
if len(opt.Description) > 255 {
return fmt.Errorf("description has more than 255 chars")
}
if len(opt.DefaultBranch) > 100 {
return fmt.Errorf("default branch name has more than 100 chars")
}
if len(opt.TrustModel) != 0 {
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
return err
}
}
return nil
}
// CreateRepo creates a repository for authenticated user.
func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) {
if err := opt.Validate(c); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("POST", "/user/repos", jsonHeader, bytes.NewReader(body), repo)
return repo, resp, err
}
// CreateOrgRepo creates an organization repository for authenticated user.
func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
if err := opt.Validate(c); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/org/%s/repos", org), jsonHeader, bytes.NewReader(body), repo)
return repo, resp, err
}
// GetRepo returns information of a repository of given owner.
func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error) {
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo)
return repo, resp, err
}
// GetRepoByID returns information of a repository by a giver repository ID.
func (c *Client) GetRepoByID(id int64) (*Repository, *Response, error) {
repo := new(Repository)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repositories/%d", id), nil, nil, repo)
return repo, resp, err
}
// EditRepoOption options when editing a repository's properties
type EditRepoOption struct {
// name of the repository
Name *string `json:"name,omitempty"`
// a short description of the repository.
Description *string `json:"description,omitempty"`
// a URL with more information about the repository.
Website *string `json:"website,omitempty"`
// either `true` to make the repository private or `false` to make it public.
// Note: you will get a 422 error if the organization restricts changing repository visibility to organization
// owners and a non-owner tries to change the value of private.
Private *bool `json:"private,omitempty"`
// either `true` to make this repository a template or `false` to make it a normal repository
Template *bool `json:"template,omitempty"`
// either `true` to enable issues for this repository or `false` to disable them.
HasIssues *bool `json:"has_issues,omitempty"`
// set this structure to configure internal issue tracker (requires has_issues)
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
// set this structure to use external issue tracker (requires has_issues)
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
// either `true` to enable the wiki for this repository or `false` to disable it.
HasWiki *bool `json:"has_wiki,omitempty"`
// set this structure to use external wiki instead of internal (requires has_wiki)
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
// sets the default branch for this repository.
DefaultBranch *string `json:"default_branch,omitempty"`
// either `true` to allow pull requests, or `false` to prevent pull request.
HasPullRequests *bool `json:"has_pull_requests,omitempty"`
// either `true` to enable project unit, or `false` to disable them.
HasProjects *bool `json:"has_projects,omitempty"`
// either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace. `has_pull_requests` must be `true`.
IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts,omitempty"`
// either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits. `has_pull_requests` must be `true`.
AllowMerge *bool `json:"allow_merge_commits,omitempty"`
// either `true` to allow rebase-merging pull requests, or `false` to prevent rebase-merging. `has_pull_requests` must be `true`.
AllowRebase *bool `json:"allow_rebase,omitempty"`
// either `true` to allow rebase with explicit merge commits (--no-ff), or `false` to prevent rebase with explicit merge commits. `has_pull_requests` must be `true`.
AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"`
// either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. `has_pull_requests` must be `true`.
AllowSquash *bool `json:"allow_squash_merge,omitempty"`
// set to `true` to archive this repository.
Archived *bool `json:"archived,omitempty"`
// set to a string like `8h30m0s` to set the mirror interval time
MirrorInterval *string `json:"mirror_interval,omitempty"`
// either `true` to allow mark pr as merged manually, or `false` to prevent it. `has_pull_requests` must be `true`.
AllowManualMerge *bool `json:"allow_manual_merge,omitempty"`
// either `true` to enable AutodetectManualMerge, or `false` to prevent it. `has_pull_requests` must be `true`, Note: In some special cases, misjudgments can occur.
AutodetectManualMerge *bool `json:"autodetect_manual_merge,omitempty"`
// set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash". `has_pull_requests` must be `true`.
DefaultMergeStyle *MergeStyle `json:"default_merge_style,omitempty"`
// set to `true` to archive this repository.
}
// EditRepo edit the properties of a repository
func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Repository, *Response, error) {
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s", owner, reponame), jsonHeader, bytes.NewReader(body), repo)
return repo, resp, err
}
// DeleteRepo deletes a repository of user or organization.
func (c *Client) DeleteRepo(owner, repo string) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s", owner, repo), nil, nil)
return resp, err
}
// MirrorSync adds a mirrored repository to the mirror sync queue.
func (c *Client) MirrorSync(owner, repo string) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/mirror-sync", owner, repo), nil, nil)
return resp, err
}
// GetRepoLanguages return language stats of a repo
func (c *Client) GetRepoLanguages(owner, repo string) (map[string]int64, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
langMap := make(map[string]int64)
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/languages", owner, repo), jsonHeader, nil)
if err != nil {
return nil, resp, err
}
if err = json.Unmarshal(data, &langMap); err != nil {
return nil, resp, err
}
return langMap, resp, nil
}
// ArchiveType represent supported archive formats by gitea
type ArchiveType string
const (
// ZipArchive represent zip format
ZipArchive ArchiveType = ".zip"
// TarGZArchive represent tar.gz format
TarGZArchive ArchiveType = ".tar.gz"
)
// GetArchive get an archive of a repository by git reference
// e.g.: ref -> master, 70b7c74b33, v1.2.1, ...
func (c *Client) GetArchive(owner, repo, ref string, ext ArchiveType) ([]byte, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
ref = pathEscapeSegments(ref)
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil)
}
// GetArchiveReader gets a `git archive` for a particular tree-ish git reference
// such as a branch name (`master`), a commit hash (`70b7c74b33`), a tag
// (`v1.2.1`). The archive is returned as a byte stream in a ReadCloser. It is
// the responsibility of the client to close the reader.
func (c *Client) GetArchiveReader(owner, repo, ref string, ext ArchiveType) (io.ReadCloser, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
ref = pathEscapeSegments(ref)
resp, err := c.doRequest("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil)
if err != nil {
return nil, resp, err
}
if _, err := statusCodeToErr(resp); err != nil {
return nil, resp, err
}
return resp.Body, resp, nil
}

View file

@ -0,0 +1,143 @@
// Copyright 2016 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"time"
)
// PayloadUser represents the author or committer of a commit
type PayloadUser struct {
// Full name of the commit author
Name string `json:"name"`
Email string `json:"email"`
UserName string `json:"username"`
}
// PayloadCommit represents a commit
type PayloadCommit struct {
// sha1 hash of the commit
ID string `json:"id"`
Message string `json:"message"`
URL string `json:"url"`
Author *PayloadUser `json:"author"`
Committer *PayloadUser `json:"committer"`
Verification *PayloadCommitVerification `json:"verification"`
Timestamp time.Time `json:"timestamp"`
Added []string `json:"added"`
Removed []string `json:"removed"`
Modified []string `json:"modified"`
}
// PayloadCommitVerification represents the GPG verification of a commit
type PayloadCommitVerification struct {
Verified bool `json:"verified"`
Reason string `json:"reason"`
Signature string `json:"signature"`
Payload string `json:"payload"`
}
// Branch represents a repository branch
type Branch struct {
Name string `json:"name"`
Commit *PayloadCommit `json:"commit"`
Protected bool `json:"protected"`
RequiredApprovals int64 `json:"required_approvals"`
EnableStatusCheck bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"`
UserCanPush bool `json:"user_can_push"`
UserCanMerge bool `json:"user_can_merge"`
EffectiveBranchProtectionName string `json:"effective_branch_protection_name"`
}
// ListRepoBranchesOptions options for listing a repository's branches
type ListRepoBranchesOptions struct {
ListOptions
}
// ListRepoBranches list all the branches of one repository
func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions) ([]*Branch, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
branches := make([]*Branch, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &branches)
return branches, resp, err
}
// GetRepoBranch get one branch's information of one repository
func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &branch); err != nil {
return nil, nil, err
}
b := new(Branch)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil, &b)
if err != nil {
return nil, resp, err
}
return b, resp, nil
}
// DeleteRepoBranch delete a branch in a repository
func (c *Client) DeleteRepoBranch(user, repo, branch string) (bool, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &branch); err != nil {
return false, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return false, nil, err
}
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil)
if err != nil {
return false, resp, err
}
return status == 204, resp, nil
}
// CreateBranchOption options when creating a branch in a repository
type CreateBranchOption struct {
// Name of the branch to create
BranchName string `json:"new_branch_name"`
// Name of the old branch to create from (optional)
OldBranchName string `json:"old_branch_name"`
}
// Validate the CreateBranchOption struct
func (opt CreateBranchOption) Validate() error {
if len(opt.BranchName) == 0 {
return fmt.Errorf("BranchName is empty")
}
if len(opt.BranchName) > 100 {
return fmt.Errorf("BranchName to long")
}
if len(opt.OldBranchName) > 100 {
return fmt.Errorf("OldBranchName to long")
}
return nil
}
// CreateBranch creates a branch for a user's repository
func (c *Client) CreateBranch(owner, repo string, opt CreateBranchOption) (*Branch, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
branch := new(Branch)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branches", owner, repo), jsonHeader, bytes.NewReader(body), branch)
return branch, resp, err
}

View file

@ -0,0 +1,173 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"time"
)
// BranchProtection represents a branch protection for a repository
type BranchProtection struct {
BranchName string `json:"branch_name"`
RuleName string `json:"rule_name"`
EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
EnableStatusCheck bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals int64 `json:"required_approvals"`
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
Created time.Time `json:"created_at"`
Updated time.Time `json:"updated_at"`
}
// CreateBranchProtectionOption options for creating a branch protection
type CreateBranchProtectionOption struct {
BranchName string `json:"branch_name"`
RuleName string `json:"rule_name"`
EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
EnableStatusCheck bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals int64 `json:"required_approvals"`
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
}
// EditBranchProtectionOption options for editing a branch protection
type EditBranchProtectionOption struct {
EnablePush *bool `json:"enable_push"`
EnablePushWhitelist *bool `json:"enable_push_whitelist"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"`
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
EnableStatusCheck *bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals *int64 `json:"required_approvals"`
EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"`
BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"`
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"`
RequireSignedCommits *bool `json:"require_signed_commits"`
ProtectedFilePatterns *string `json:"protected_file_patterns"`
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
}
// ListBranchProtectionsOptions list branch protection options
type ListBranchProtectionsOptions struct {
ListOptions
}
// ListBranchProtections list branch protections for a repo
func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtectionsOptions) ([]*BranchProtection, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
bps := make([]*BranchProtection, 0, opt.PageSize)
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo))
link.RawQuery = opt.getURLQuery().Encode()
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &bps)
return bps, resp, err
}
// GetBranchProtection gets a branch protection
func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtection, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
bp := new(BranchProtection)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil, bp)
return bp, resp, err
}
// CreateBranchProtection creates a branch protection for a repo
func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProtectionOption) (*BranchProtection, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
bp := new(BranchProtection)
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo), jsonHeader, bytes.NewReader(body), bp)
return bp, resp, err
}
// EditBranchProtection edits a branch protection for a repo
func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchProtectionOption) (*BranchProtection, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
bp := new(BranchProtection)
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), bp)
return bp, resp, err
}
// DeleteBranchProtection deletes a branch protection for a repo
func (c *Client) DeleteBranchProtection(owner, repo, name string) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
return nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil)
return resp, err
}

View file

@ -0,0 +1,163 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Copyright 2016 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// ListCollaboratorsOptions options for listing a repository's collaborators
type ListCollaboratorsOptions struct {
ListOptions
}
// CollaboratorPermissionResult result type for CollaboratorPermission
type CollaboratorPermissionResult struct {
Permission AccessMode `json:"permission"`
Role string `json:"role_name"`
User *User `json:"user"`
}
// ListCollaborators list a repository's collaborators
func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptions) ([]*User, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
collaborators := make([]*User, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET",
fmt.Sprintf("/repos/%s/%s/collaborators?%s", user, repo, opt.getURLQuery().Encode()),
nil, nil, &collaborators)
return collaborators, resp, err
}
// IsCollaborator check if a user is a collaborator of a repository
func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
return false, nil, err
}
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
if err != nil {
return false, resp, err
}
if status == 204 {
return true, resp, nil
}
return false, resp, nil
}
// CollaboratorPermission gets collaborator permission of a repository
func (c *Client) CollaboratorPermission(user, repo, collaborator string) (*CollaboratorPermissionResult, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
return nil, nil, err
}
rv := new(CollaboratorPermissionResult)
resp, err := c.getParsedResponse("GET",
fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", user, repo, collaborator),
nil,
nil,
rv)
if err != nil {
return nil, resp, err
}
if resp.StatusCode != 200 {
rv = nil
}
return rv, resp, nil
}
// AddCollaboratorOption options when adding a user as a collaborator of a repository
type AddCollaboratorOption struct {
Permission *AccessMode `json:"permission"`
}
// AccessMode represent the grade of access you have to something
type AccessMode string
const (
// AccessModeNone no access
AccessModeNone AccessMode = "none"
// AccessModeRead read access
AccessModeRead AccessMode = "read"
// AccessModeWrite write access
AccessModeWrite AccessMode = "write"
// AccessModeAdmin admin access
AccessModeAdmin AccessMode = "admin"
// AccessModeOwner owner
AccessModeOwner AccessMode = "owner"
)
// Validate the AddCollaboratorOption struct
func (opt *AddCollaboratorOption) Validate() error {
if opt.Permission != nil {
if *opt.Permission == AccessModeOwner {
*opt.Permission = AccessModeAdmin
return nil
}
if *opt.Permission == AccessModeNone {
opt.Permission = nil
return nil
}
if *opt.Permission != AccessModeRead && *opt.Permission != AccessModeWrite && *opt.Permission != AccessModeAdmin {
return fmt.Errorf("permission mode invalid")
}
}
return nil
}
// AddCollaborator add some user as a collaborator of a repository
func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollaboratorOption) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
return nil, err
}
if err := (&opt).Validate(); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), jsonHeader, bytes.NewReader(body))
return resp, err
}
// DeleteCollaborator remove a collaborator from a repository
func (c *Client) DeleteCollaborator(user, repo, collaborator string) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE",
fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
return resp, err
}
// GetReviewers return all users that can be requested to review in this repo
func (c *Client) GetReviewers(user, repo string) ([]*User, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
reviewers := make([]*User, 0, 5)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/reviewers", user, repo), nil, nil, &reviewers)
return reviewers, resp, err
}
// GetAssignees return all users that have write access and can be assigned to issues
func (c *Client) GetAssignees(user, repo string) ([]*User, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
assignees := make([]*User, 0, 5)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/assignees", user, repo), nil, nil, &assignees)
return assignees, resp, err
}

View file

@ -0,0 +1,141 @@
// Copyright 2018 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"net/url"
"time"
)
// Identity for a person's identity like an author or committer
type Identity struct {
Name string `json:"name"`
Email string `json:"email"`
}
// CommitMeta contains meta information of a commit in terms of API.
type CommitMeta struct {
URL string `json:"url"`
SHA string `json:"sha"`
Created time.Time `json:"created"`
}
// CommitUser contains information of a user in the context of a commit.
type CommitUser struct {
Identity
Date string `json:"date"`
}
// RepoCommit contains information of a commit in the context of a repository.
type RepoCommit struct {
URL string `json:"url"`
Author *CommitUser `json:"author"`
Committer *CommitUser `json:"committer"`
Message string `json:"message"`
Tree *CommitMeta `json:"tree"`
Verification *PayloadCommitVerification `json:"verification"`
}
// CommitStats contains stats from a Git commit
type CommitStats struct {
Total int `json:"total"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
}
// Commit contains information generated from a Git commit.
type Commit struct {
*CommitMeta
HTMLURL string `json:"html_url"`
RepoCommit *RepoCommit `json:"commit"`
Author *User `json:"author"`
Committer *User `json:"committer"`
Parents []*CommitMeta `json:"parents"`
Files []*CommitAffectedFiles `json:"files"`
Stats *CommitStats `json:"stats"`
}
// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
type CommitDateOptions struct {
Author time.Time `json:"author"`
Committer time.Time `json:"committer"`
}
// CommitAffectedFiles store information about files affected by the commit
type CommitAffectedFiles struct {
Filename string `json:"filename"`
}
// GetSingleCommit returns a single commit
func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &commitID); err != nil {
return nil, nil, err
}
commit := new(Commit)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s", user, repo, commitID), nil, nil, &commit)
return commit, resp, err
}
// ListCommitOptions list commit options
type ListCommitOptions struct {
ListOptions
// SHA or branch to start listing commits from (usually 'master')
SHA string
// Path indicates that only commits that include the path's file/dir should be returned.
Path string
}
// QueryEncode turns options into querystring argument
func (opt *ListCommitOptions) QueryEncode() string {
query := opt.getURLQuery()
if opt.SHA != "" {
query.Add("sha", opt.SHA)
}
if opt.Path != "" {
query.Add("path", opt.Path)
}
return query.Encode()
}
// ListRepoCommits return list of commits from a repo
func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*Commit, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/commits", user, repo))
opt.setDefaults()
commits := make([]*Commit, 0, opt.PageSize)
link.RawQuery = opt.QueryEncode()
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits)
return commits, resp, err
}
// GetCommitDiff returns the commit's raw diff.
func (c *Client) GetCommitDiff(user, repo, commitID string) ([]byte, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s.%s", user, repo, commitID, pullRequestDiffTypeDiff), nil, nil)
}
// GetCommitPatch returns the commit's raw patch.
func (c *Client) GetCommitPatch(user, repo, commitID string) ([]byte, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s.%s", user, repo, commitID, pullRequestDiffTypePatch), nil, nil)
}

View file

@ -0,0 +1,277 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/url"
"strings"
)
// FileOptions options for all file APIs
type FileOptions struct {
// message (optional) for the commit of this file. if not supplied, a default message will be used
Message string `json:"message"`
// branch (optional) to base this file from. if not given, the default branch is used
BranchName string `json:"branch"`
// new_branch (optional) will make a new branch from `branch` before creating the file
NewBranchName string `json:"new_branch"`
// `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
Author Identity `json:"author"`
Committer Identity `json:"committer"`
Dates CommitDateOptions `json:"dates"`
// Add a Signed-off-by trailer by the committer at the end of the commit log message.
Signoff bool `json:"signoff"`
}
// CreateFileOptions options for creating files
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type CreateFileOptions struct {
FileOptions
// content must be base64 encoded
// required: true
Content string `json:"content"`
}
// DeleteFileOptions options for deleting files (used for other File structs below)
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type DeleteFileOptions struct {
FileOptions
// sha is the SHA for the file that already exists
// required: true
SHA string `json:"sha"`
}
// UpdateFileOptions options for updating files
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
type UpdateFileOptions struct {
FileOptions
// sha is the SHA for the file that already exists
// required: true
SHA string `json:"sha"`
// content must be base64 encoded
// required: true
Content string `json:"content"`
// from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL
FromPath string `json:"from_path"`
}
// FileLinksResponse contains the links for a repo's file
type FileLinksResponse struct {
Self *string `json:"self"`
GitURL *string `json:"git"`
HTMLURL *string `json:"html"`
}
// ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content
type ContentsResponse struct {
Name string `json:"name"`
Path string `json:"path"`
SHA string `json:"sha"`
// `type` will be `file`, `dir`, `symlink`, or `submodule`
Type string `json:"type"`
Size int64 `json:"size"`
// `encoding` is populated when `type` is `file`, otherwise null
Encoding *string `json:"encoding"`
// `content` is populated when `type` is `file`, otherwise null
Content *string `json:"content"`
// `target` is populated when `type` is `symlink`, otherwise null
Target *string `json:"target"`
URL *string `json:"url"`
HTMLURL *string `json:"html_url"`
GitURL *string `json:"git_url"`
DownloadURL *string `json:"download_url"`
// `submodule_git_url` is populated when `type` is `submodule`, otherwise null
SubmoduleGitURL *string `json:"submodule_git_url"`
Links *FileLinksResponse `json:"_links"`
}
// FileCommitResponse contains information generated from a Git commit for a repo's file.
type FileCommitResponse struct {
CommitMeta
HTMLURL string `json:"html_url"`
Author *CommitUser `json:"author"`
Committer *CommitUser `json:"committer"`
Parents []*CommitMeta `json:"parents"`
Message string `json:"message"`
Tree *CommitMeta `json:"tree"`
}
// FileResponse contains information about a repo's file
type FileResponse struct {
Content *ContentsResponse `json:"content"`
Commit *FileCommitResponse `json:"commit"`
Verification *PayloadCommitVerification `json:"verification"`
}
// FileDeleteResponse contains information about a repo's file that was deleted
type FileDeleteResponse struct {
Content interface{} `json:"content"` // to be set to nil
Commit *FileCommitResponse `json:"commit"`
Verification *PayloadCommitVerification `json:"verification"`
}
// GetFile downloads a file of repository, ref can be branch/tag/commit.
// it optional can resolve lfs pointers and server the file instead
// e.g.: ref -> master, filepath -> README.md (no leading slash)
func (c *Client) GetFile(owner, repo, ref, filepath string, resolveLFS ...bool) ([]byte, *Response, error) {
reader, resp, err := c.GetFileReader(owner, repo, ref, filepath, resolveLFS...)
if reader == nil {
return nil, resp, err
}
defer reader.Close()
data, err2 := io.ReadAll(reader)
if err2 != nil {
return nil, resp, err2
}
return data, resp, err
}
// GetFileReader return reader for download a file of repository, ref can be branch/tag/commit.
// it optional can resolve lfs pointers and server the file instead
// e.g.: ref -> master, filepath -> README.md (no leading slash)
func (c *Client) GetFileReader(owner, repo, ref, filepath string, resolveLFS ...bool) (io.ReadCloser, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
// resolve lfs
if len(resolveLFS) != 0 && resolveLFS[0] {
if err := c.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil {
return nil, nil, err
}
return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/media/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil)
}
// normal get
filepath = pathEscapeSegments(filepath)
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
ref = pathEscapeSegments(ref)
return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", owner, repo, ref, filepath), nil, nil)
}
return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/raw/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil)
}
// GetContents get the metadata and contents of a file in a repository
// ref is optional
func (c *Client) GetContents(owner, repo, ref, filepath string) (*ContentsResponse, *Response, error) {
data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath)
if err != nil {
return nil, resp, err
}
cr := new(ContentsResponse)
if json.Unmarshal(data, &cr) != nil {
return nil, resp, fmt.Errorf("expect file, got directory")
}
return cr, resp, err
}
// ListContents gets a list of entries in a dir
// ref is optional
func (c *Client) ListContents(owner, repo, ref, filepath string) ([]*ContentsResponse, *Response, error) {
data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath)
if err != nil {
return nil, resp, err
}
crl := make([]*ContentsResponse, 0)
if json.Unmarshal(data, &crl) != nil {
return nil, resp, fmt.Errorf("expect directory, got file")
}
return crl, resp, err
}
func (c *Client) getDirOrFileContents(owner, repo, ref, filepath string) ([]byte, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
filepath = pathEscapeSegments(strings.TrimPrefix(filepath, "/"))
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/contents/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), jsonHeader, nil)
}
// CreateFile create a file in a repository
func (c *Client) CreateFile(owner, repo, filepath string, opt CreateFileOptions) (*FileResponse, *Response, error) {
var err error
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
filepath = pathEscapeSegments(filepath)
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
fr := new(FileResponse)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr)
return fr, resp, err
}
// UpdateFile update a file in a repository
func (c *Client) UpdateFile(owner, repo, filepath string, opt UpdateFileOptions) (*FileResponse, *Response, error) {
var err error
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
filepath = pathEscapeSegments(filepath)
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
fr := new(FileResponse)
resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr)
return fr, resp, err
}
// DeleteFile delete a file from repository
func (c *Client) DeleteFile(owner, repo, filepath string, opt DeleteFileOptions) (*Response, error) {
var err error
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
return nil, err
}
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
filepath = pathEscapeSegments(filepath)
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body))
if err != nil {
return resp, err
}
if status != 200 && status != 204 {
return resp, fmt.Errorf("unexpected Status: %d", status)
}
return resp, nil
}
func (c *Client) setDefaultBranchForOldVersions(owner, repo, branch string) (string, error) {
if len(branch) == 0 {
// Gitea >= 1.12.0 Use DefaultBranch on "", mimic this for older versions
if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil {
r, _, err := c.GetRepo(owner, repo)
if err != nil {
return "", err
}
return r.DefaultBranch, nil
}
}
return branch, nil
}

View file

@ -0,0 +1,91 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"time"
)
// DeployKey a deploy key
type DeployKey struct {
ID int64 `json:"id"`
KeyID int64 `json:"key_id"`
Key string `json:"key"`
URL string `json:"url"`
Title string `json:"title"`
Fingerprint string `json:"fingerprint"`
Created time.Time `json:"created_at"`
ReadOnly bool `json:"read_only"`
Repository *Repository `json:"repository,omitempty"`
}
// ListDeployKeysOptions options for listing a repository's deploy keys
type ListDeployKeysOptions struct {
ListOptions
KeyID int64
Fingerprint string
}
// QueryEncode turns options into querystring argument
func (opt *ListDeployKeysOptions) QueryEncode() string {
query := opt.getURLQuery()
if opt.KeyID > 0 {
query.Add("key_id", fmt.Sprintf("%d", opt.KeyID))
}
if len(opt.Fingerprint) > 0 {
query.Add("fingerprint", opt.Fingerprint)
}
return query.Encode()
}
// ListDeployKeys list all the deploy keys of one repository
func (c *Client) ListDeployKeys(user, repo string, opt ListDeployKeysOptions) ([]*DeployKey, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/keys", user, repo))
opt.setDefaults()
link.RawQuery = opt.QueryEncode()
keys := make([]*DeployKey, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &keys)
return keys, resp, err
}
// GetDeployKey get one deploy key with key id
func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
key := new(DeployKey)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/keys/%d", user, repo, keyID), nil, nil, &key)
return key, resp, err
}
// CreateDeployKey options when create one deploy key
func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*DeployKey, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
key := new(DeployKey)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/keys", user, repo), jsonHeader, bytes.NewReader(body), key)
return key, resp, err
}
// DeleteDeployKey delete deploy key with key id
func (c *Client) DeleteDeployKey(owner, repo string, keyID int64) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/keys/%d", owner, repo, keyID), nil, nil)
return resp, err
}

View file

@ -0,0 +1,132 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// GitServiceType represents a git service
type GitServiceType string
const (
// GitServicePlain represents a plain git service
GitServicePlain GitServiceType = "git"
// GitServiceGithub represents github.com
GitServiceGithub GitServiceType = "github"
// GitServiceGitlab represents a gitlab service
GitServiceGitlab GitServiceType = "gitlab"
// GitServiceGitea represents a gitea service
GitServiceGitea GitServiceType = "gitea"
// GitServiceGogs represents a gogs service
GitServiceGogs GitServiceType = "gogs"
)
// MigrateRepoOption options for migrating a repository from an external service
type MigrateRepoOption struct {
RepoName string `json:"repo_name"`
RepoOwner string `json:"repo_owner"`
// deprecated use RepoOwner
RepoOwnerID int64 `json:"uid"`
CloneAddr string `json:"clone_addr"`
Service GitServiceType `json:"service"`
AuthUsername string `json:"auth_username"`
AuthPassword string `json:"auth_password"`
AuthToken string `json:"auth_token"`
Mirror bool `json:"mirror"`
Private bool `json:"private"`
Description string `json:"description"`
Wiki bool `json:"wiki"`
Milestones bool `json:"milestones"`
Labels bool `json:"labels"`
Issues bool `json:"issues"`
PullRequests bool `json:"pull_requests"`
Releases bool `json:"releases"`
MirrorInterval string `json:"mirror_interval"`
LFS bool `json:"lfs"`
LFSEndpoint string `json:"lfs_endpoint"`
}
// Validate the MigrateRepoOption struct
func (opt *MigrateRepoOption) Validate(c *Client) error {
// check user options
if len(opt.CloneAddr) == 0 {
return fmt.Errorf("CloneAddr required")
}
if len(opt.RepoName) == 0 {
return fmt.Errorf("RepoName required")
} else if len(opt.RepoName) > 100 {
return fmt.Errorf("RepoName to long")
}
if len(opt.Description) > 255 {
return fmt.Errorf("Description to long")
}
switch opt.Service {
case GitServiceGithub:
if len(opt.AuthToken) == 0 {
return fmt.Errorf("github requires token authentication")
}
case GitServiceGitlab, GitServiceGitea:
if len(opt.AuthToken) == 0 {
return fmt.Errorf("%s requires token authentication", opt.Service)
}
// Gitlab is supported since 1.12.0 but api cant handle it until 1.13.0
// https://github.com/go-gitea/gitea/pull/12672
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
return fmt.Errorf("migrate from service %s need gitea >= 1.13.0", opt.Service)
}
case GitServiceGogs:
if len(opt.AuthToken) == 0 {
return fmt.Errorf("gogs requires token authentication")
}
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
return fmt.Errorf("migrate from service gogs need gitea >= 1.14.0")
}
}
return nil
}
// MigrateRepo migrates a repository from other Git hosting sources for the authenticated user.
//
// To migrate a repository for a organization, the authenticated user must be a
// owner of the specified organization.
func (c *Client) MigrateRepo(opt MigrateRepoOption) (*Repository, *Response, error) {
if err := opt.Validate(c); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
if len(opt.AuthToken) != 0 {
// gitea <= 1.12 dont understand AuthToken
opt.AuthUsername = opt.AuthToken
opt.AuthPassword, opt.AuthToken = "", ""
}
if len(opt.RepoOwner) != 0 {
// gitea <= 1.12 dont understand RepoOwner
u, _, err := c.GetUserInfo(opt.RepoOwner)
if err != nil {
return nil, nil, err
}
opt.RepoOwnerID = u.ID
} else if opt.RepoOwnerID == 0 {
// gitea <= 1.12 require RepoOwnerID
u, _, err := c.GetMyUserInfo()
if err != nil {
return nil, nil, err
}
opt.RepoOwnerID = u.ID
}
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("POST", "/repos/migrate", jsonHeader, bytes.NewReader(body), repo)
return repo, resp, err
}

View file

@ -0,0 +1,78 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
// Reference represents a Git reference.
type Reference struct {
Ref string `json:"ref"`
URL string `json:"url"`
Object *GitObject `json:"object"`
}
// GitObject represents a Git object.
type GitObject struct {
Type string `json:"type"`
SHA string `json:"sha"`
URL string `json:"url"`
}
// GetRepoRef get one ref's information of one repository
func (c *Client) GetRepoRef(user, repo, ref string) (*Reference, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
ref = strings.TrimPrefix(ref, "refs/")
ref = pathEscapeSegments(ref)
r := new(Reference)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil, &r)
if _, ok := err.(*json.UnmarshalTypeError); ok {
// Multiple refs
return nil, resp, errors.New("no exact match found for this ref")
} else if err != nil {
return nil, resp, err
}
return r, resp, nil
}
// GetRepoRefs get list of ref's information of one repository
func (c *Client) GetRepoRefs(user, repo, ref string) ([]*Reference, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
ref = strings.TrimPrefix(ref, "refs/")
ref = pathEscapeSegments(ref)
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil)
if err != nil {
return nil, resp, err
}
// Attempt to unmarshal single returned ref.
r := new(Reference)
refErr := json.Unmarshal(data, r)
if refErr == nil {
return []*Reference{r}, resp, nil
}
// Attempt to unmarshal multiple refs.
var rs []*Reference
refsErr := json.Unmarshal(data, &rs)
if refsErr == nil {
if len(rs) == 0 {
return nil, resp, errors.New("unexpected response: an array of refs with length 0")
}
return rs, resp, nil
}
return nil, resp, fmt.Errorf("unmarshalling failed for both single and multiple refs: %s and %s", refErr, refsErr)
}

View file

@ -0,0 +1,96 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"net/http"
)
// ListStargazersOptions options for listing a repository's stargazers
type ListStargazersOptions struct {
ListOptions
}
// ListRepoStargazers list a repository's stargazers
func (c *Client) ListRepoStargazers(user, repo string, opt ListStargazersOptions) ([]*User, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
stargazers := make([]*User, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/stargazers?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &stargazers)
return stargazers, resp, err
}
// GetStarredRepos returns the repos that the given user has starred
func (c *Client) GetStarredRepos(user string) ([]*Repository, *Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, nil, err
}
repos := make([]*Repository, 0, 10)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/starred", user), jsonHeader, nil, &repos)
return repos, resp, err
}
// GetMyStarredRepos returns the repos that the authenticated user has starred
func (c *Client) GetMyStarredRepos() ([]*Repository, *Response, error) {
repos := make([]*Repository, 0, 10)
resp, err := c.getParsedResponse("GET", "/user/starred", jsonHeader, nil, &repos)
return repos, resp, err
}
// IsRepoStarring returns whether the authenticated user has starred the repo or not
func (c *Client) IsRepoStarring(user, repo string) (bool, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return false, nil, err
}
_, resp, err := c.getResponse("GET", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
if resp != nil {
switch resp.StatusCode {
case http.StatusNotFound:
return false, resp, nil
case http.StatusNoContent:
return true, resp, nil
default:
return false, resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
}
}
return false, nil, err
}
// StarRepo star specified repo as the authenticated user
func (c *Client) StarRepo(user, repo string) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
if resp != nil {
switch resp.StatusCode {
case http.StatusNoContent:
return resp, nil
default:
return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
}
}
return nil, err
}
// UnStarRepo remove star to specified repo as the authenticated user
func (c *Client) UnStarRepo(user, repo string) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
if resp != nil {
switch resp.StatusCode {
case http.StatusNoContent:
return resp, nil
default:
return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
}
}
return nil, err
}

View file

@ -0,0 +1,130 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// Tag represents a repository tag
type Tag struct {
Name string `json:"name"`
Message string `json:"message"`
ID string `json:"id"`
Commit *CommitMeta `json:"commit"`
ZipballURL string `json:"zipball_url"`
TarballURL string `json:"tarball_url"`
}
// AnnotatedTag represents an annotated tag
type AnnotatedTag struct {
Tag string `json:"tag"`
SHA string `json:"sha"`
URL string `json:"url"`
Message string `json:"message"`
Tagger *CommitUser `json:"tagger"`
Object *AnnotatedTagObject `json:"object"`
Verification *PayloadCommitVerification `json:"verification"`
}
// AnnotatedTagObject contains meta information of the tag object
type AnnotatedTagObject struct {
Type string `json:"type"`
URL string `json:"url"`
SHA string `json:"sha"`
}
// ListRepoTagsOptions options for listing a repository's tags
type ListRepoTagsOptions struct {
ListOptions
}
// ListRepoTags list all the branches of one repository
func (c *Client) ListRepoTags(user, repo string, opt ListRepoTagsOptions) ([]*Tag, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
tags := make([]*Tag, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &tags)
return tags, resp, err
}
// GetTag get the tag of a repository
func (c *Client) GetTag(user, repo, tag string) (*Tag, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
return nil, nil, err
}
t := new(Tag)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag), nil, nil, &t)
return t, resp, err
}
// GetAnnotatedTag get the tag object of an annotated tag (not lightweight tags) of a repository
func (c *Client) GetAnnotatedTag(user, repo, sha string) (*AnnotatedTag, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil {
return nil, nil, err
}
t := new(AnnotatedTag)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/tags/%s", user, repo, sha), nil, nil, &t)
return t, resp, err
}
// CreateTagOption options when creating a tag
type CreateTagOption struct {
TagName string `json:"tag_name"`
Message string `json:"message"`
Target string `json:"target"`
}
// Validate validates CreateTagOption
func (opt CreateTagOption) Validate() error {
if len(opt.TagName) == 0 {
return fmt.Errorf("TagName is required")
}
return nil
}
// CreateTag create a new git tag in a repository
func (c *Client) CreateTag(user, repo string, opt CreateTagOption) (*Tag, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(opt)
if err != nil {
return nil, nil, err
}
t := new(Tag)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/tags", user, repo), jsonHeader, bytes.NewReader(body), &t)
return t, resp, err
}
// DeleteTag deletes a tag from a repository, if no release refers to it
func (c *Client) DeleteTag(user, repo, tag string) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
return nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE",
fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag),
nil, nil)
return resp, err
}

View file

@ -0,0 +1,65 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"net/http"
)
// GetRepoTeams return teams from a repository
func (c *Client) GetRepoTeams(user, repo string) ([]*Team, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
teams := make([]*Team, 0, 5)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/teams", user, repo), nil, nil, &teams)
return teams, resp, err
}
// AddRepoTeam add a team to a repository
func (c *Client) AddRepoTeam(user, repo, team string) (*Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
return nil, err
}
if err := escapeValidatePathSegments(&user, &repo, &team); err != nil {
return nil, err
}
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil)
return resp, err
}
// RemoveRepoTeam delete a team from a repository
func (c *Client) RemoveRepoTeam(user, repo, team string) (*Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
return nil, err
}
if err := escapeValidatePathSegments(&user, &repo, &team); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil)
return resp, err
}
// CheckRepoTeam check if team is assigned to repo by name and return it.
// If not assigned, it will return nil.
func (c *Client) CheckRepoTeam(user, repo, team string) (*Team, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo, &team); err != nil {
return nil, nil, err
}
t := new(Team)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil, &t)
if resp != nil && resp.StatusCode == http.StatusNotFound {
// if not found it's not an error, it indicates it's not assigned
return nil, resp, nil
}
return t, resp, err
}

View file

@ -0,0 +1,65 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// CreateRepoFromTemplateOption options when creating repository using a template
type CreateRepoFromTemplateOption struct {
// Owner is the organization or person who will own the new repository
Owner string `json:"owner"`
// Name of the repository to create
Name string `json:"name"`
// Description of the repository to create
Description string `json:"description"`
// Private is whether the repository is private
Private bool `json:"private"`
// GitContent include git content of default branch in template repo
GitContent bool `json:"git_content"`
// Topics include topics of template repo
Topics bool `json:"topics"`
// GitHooks include git hooks of template repo
GitHooks bool `json:"git_hooks"`
// Webhooks include webhooks of template repo
Webhooks bool `json:"webhooks"`
// Avatar include avatar of the template repo
Avatar bool `json:"avatar"`
// Labels include labels of template repo
Labels bool `json:"labels"`
}
// Validate validates CreateRepoFromTemplateOption
func (opt CreateRepoFromTemplateOption) Validate() error {
if len(opt.Owner) == 0 {
return fmt.Errorf("field Owner is required")
}
if len(opt.Name) == 0 {
return fmt.Errorf("field Name is required")
}
return nil
}
// CreateRepoFromTemplate create a repository using a template
func (c *Client) CreateRepoFromTemplate(templateOwner, templateRepo string, opt CreateRepoFromTemplateOption) (*Repository, *Response, error) {
if err := escapeValidatePathSegments(&templateOwner, &templateRepo); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/generate", templateOwner, templateRepo), jsonHeader, bytes.NewReader(body), &repo)
return repo, resp, err
}

View file

@ -0,0 +1,68 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// ListRepoTopicsOptions options for listing repo's topics
type ListRepoTopicsOptions struct {
ListOptions
}
// topicsList represents a list of repo's topics
type topicsList struct {
Topics []string `json:"topics"`
}
// ListRepoTopics list all repository's topics
func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([]string, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
list := new(topicsList)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/topics?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, list)
if err != nil {
return nil, resp, err
}
return list.Topics, resp, nil
}
// SetRepoTopics replaces the list of repo's topics
func (c *Client) SetRepoTopics(user, repo string, list []string) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, err
}
l := topicsList{Topics: list}
body, err := json.Marshal(&l)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics", user, repo), jsonHeader, bytes.NewReader(body))
return resp, err
}
// AddRepoTopic adds a topic to a repo's topics list
func (c *Client) AddRepoTopic(user, repo, topic string) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil {
return nil, err
}
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil)
return resp, err
}
// DeleteRepoTopic deletes a topic from repo's topics list
func (c *Client) DeleteRepoTopic(user, repo, topic string) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil)
return resp, err
}

View file

@ -0,0 +1,62 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"bytes"
"encoding/json"
"fmt"
)
// TransferRepoOption options when transfer a repository's ownership
type TransferRepoOption struct {
// required: true
NewOwner string `json:"new_owner"`
// ID of the team or teams to add to the repository. Teams can only be added to organization-owned repositories.
TeamIDs *[]int64 `json:"team_ids"`
}
// TransferRepo transfers the ownership of a repository
func (c *Client) TransferRepo(owner, reponame string, opt TransferRepoOption) (*Repository, *Response, error) {
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer", owner, reponame), jsonHeader, bytes.NewReader(body), repo)
return repo, resp, err
}
// AcceptRepoTransfer accepts a repo transfer.
func (c *Client) AcceptRepoTransfer(owner, reponame string) (*Repository, *Response, error) {
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer/accept", owner, reponame), jsonHeader, nil, repo)
return repo, resp, err
}
// RejectRepoTransfer rejects a repo transfer.
func (c *Client) RejectRepoTransfer(owner, reponame string) (*Repository, *Response, error) {
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer/reject", owner, reponame), jsonHeader, nil, repo)
return repo, resp, err
}

View file

@ -0,0 +1,44 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
)
// GitEntry represents a git tree
type GitEntry struct {
Path string `json:"path"`
Mode string `json:"mode"`
Type string `json:"type"`
Size int64 `json:"size"`
SHA string `json:"sha"`
URL string `json:"url"`
}
// GitTreeResponse returns a git tree
type GitTreeResponse struct {
SHA string `json:"sha"`
URL string `json:"url"`
Entries []GitEntry `json:"tree"`
Truncated bool `json:"truncated"`
Page int `json:"page"`
TotalCount int `json:"total_count"`
}
// GetTrees downloads a file of repository, ref can be branch/tag/commit.
// e.g.: ref -> master, tree -> macaron.go(no leading slash)
func (c *Client) GetTrees(user, repo, ref string, recursive bool) (*GitTreeResponse, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &ref); err != nil {
return nil, nil, err
}
trees := new(GitTreeResponse)
path := fmt.Sprintf("/repos/%s/%s/git/trees/%s", user, repo, ref)
if recursive {
path += "?recursive=1"
}
resp, err := c.getParsedResponse("GET", path, nil, nil, trees)
return trees, resp, err
}

View file

@ -0,0 +1,87 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sdk
import (
"fmt"
"net/http"
"time"
)
// WatchInfo represents an API watch status of one repository
type WatchInfo struct {
Subscribed bool `json:"subscribed"`
Ignored bool `json:"ignored"`
Reason interface{} `json:"reason"`
CreatedAt time.Time `json:"created_at"`
URL string `json:"url"`
RepositoryURL string `json:"repository_url"`
}
// GetWatchedRepos list all the watched repos of user
func (c *Client) GetWatchedRepos(user string) ([]*Repository, *Response, error) {
if err := escapeValidatePathSegments(&user); err != nil {
return nil, nil, err
}
repos := make([]*Repository, 0, 10)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/subscriptions", user), nil, nil, &repos)
return repos, resp, err
}
// GetMyWatchedRepos list repositories watched by the authenticated user
func (c *Client) GetMyWatchedRepos() ([]*Repository, *Response, error) {
repos := make([]*Repository, 0, 10)
resp, err := c.getParsedResponse("GET", "/user/subscriptions", nil, nil, &repos)
return repos, resp, err
}
// CheckRepoWatch check if the current user is watching a repo
func (c *Client) CheckRepoWatch(owner, repo string) (bool, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return false, nil, err
}
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil)
if err != nil {
return false, resp, err
}
switch status {
case http.StatusNotFound:
return false, resp, nil
case http.StatusOK:
return true, resp, nil
default:
return false, resp, fmt.Errorf("unexpected Status: %d", status)
}
}
// WatchRepo start to watch a repository
func (c *Client) WatchRepo(owner, repo string) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil)
if err != nil {
return resp, err
}
if status == http.StatusOK {
return resp, nil
}
return resp, fmt.Errorf("unexpected Status: %d", status)
}
// UnWatchRepo stop to watch a repository
func (c *Client) UnWatchRepo(owner, repo string) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
}
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil)
if err != nil {
return resp, err
}
if status == http.StatusNoContent {
return resp, nil
}
return resp, fmt.Errorf("unexpected Status: %d", status)
}

Some files were not shown because too many files have changed in this diff Show more