1
0
Fork 0

feat(F3): CLI: f3 mirror to convert to/from Forgejo

feat(F3): driver stub

feat(F3): util.Logger

feat(F3): driver compliance tests

feat(F3): driver/users implementation

feat(F3): driver/user implementation

feat(F3): driver/{projects,project} implementation

feat(F3): driver/{labels,label} implementation

feat(F3): driver/{milestones,milestone} implementation

feat(F3): driver/{repositories,repository} implementation

feat(F3): driver/{organizations,organization} implementation

feat(F3): driver/{releases,release} implementation

feat(F3): driver/{issues,issue} implementation

feat(F3): driver/{comments,comment} implementation

feat(F3): driver/{assets,asset} implementation

feat(F3): driver/{pullrequests,pullrequest} implementation

feat(F3): driver/{reviews,review} implementation

feat(F3): driver/{topics,topic} implementation

feat(F3): driver/{reactions,reaction} implementation

feat(F3): driver/{reviewComments,reviewComment} implementation

feat(F3): CLI: f3 mirror

chore(F3): move to code.forgejo.org

feat(f3): upgrade to gof3 3.1.0

repositories in pull requests are represented with a reference instead
of an owner/project pair of names
This commit is contained in:
Earl Warren 2024-01-23 09:43:29 +00:00
parent 64b67ba641
commit e99d3f7055
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
56 changed files with 3991 additions and 5 deletions

File diff suppressed because one or more lines are too long

View file

@ -134,8 +134,8 @@ func validateSecret(secret string) error {
} }
func RunRegister(ctx context.Context, cliCtx *cli.Context) error { func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
var cancel context.CancelFunc
if !ContextGetNoInit(ctx) { if !ContextGetNoInit(ctx) {
var cancel context.CancelFunc
ctx, cancel = installSignals(ctx) ctx, cancel = installSignals(ctx)
defer cancel() defer cancel()

70
cmd/forgejo/f3.go Normal file
View file

@ -0,0 +1,70 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package forgejo
import (
"context"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/services/f3/util"
_ "code.gitea.io/gitea/services/f3/driver" // register the driver
f3_cmd "code.forgejo.org/f3/gof3/v3/cmd"
f3_logger "code.forgejo.org/f3/gof3/v3/logger"
f3_util "code.forgejo.org/f3/gof3/v3/util"
"github.com/urfave/cli/v2"
)
func CmdF3(ctx context.Context) *cli.Command {
ctx = f3_logger.ContextSetLogger(ctx, util.NewF3Logger(nil, log.GetLogger(log.DEFAULT)))
return &cli.Command{
Name: "f3",
Usage: "F3",
Subcommands: []*cli.Command{
SubcmdF3Mirror(ctx),
},
}
}
func SubcmdF3Mirror(ctx context.Context) *cli.Command {
mirrorCmd := f3_cmd.CreateCmdMirror(ctx)
mirrorCmd.Before = prepareWorkPathAndCustomConf(ctx)
f3Action := mirrorCmd.Action
mirrorCmd.Action = func(c *cli.Context) error { return runMirror(ctx, c, f3Action) }
return mirrorCmd
}
func runMirror(ctx context.Context, c *cli.Context, action cli.ActionFunc) error {
var cancel context.CancelFunc
if !ContextGetNoInit(ctx) {
ctx, cancel = installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
if err := storage.Init(); err != nil {
return err
}
if err := git.InitSimple(ctx); err != nil {
return err
}
if err := models.Init(ctx); err != nil {
return err
}
}
err := action(c)
if panicError, ok := err.(f3_util.PanicError); ok {
log.Debug("F3 Stack trace\n%s", panicError.Stack())
}
return err
}

View file

@ -36,6 +36,7 @@ func CmdForgejo(ctx context.Context) *cli.Command {
Flags: []cli.Flag{}, Flags: []cli.Flag{},
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
CmdActions(ctx), CmdActions(ctx),
CmdF3(ctx),
}, },
} }
} }

View file

@ -124,6 +124,7 @@ func NewMainApp(version, versionExtra string) *cli.App {
var subCmdsStandalone []*cli.Command = make([]*cli.Command, 0, 10) var subCmdsStandalone []*cli.Command = make([]*cli.Command, 0, 10)
var subCmdWithConfig []*cli.Command = make([]*cli.Command, 0, 10) var subCmdWithConfig []*cli.Command = make([]*cli.Command, 0, 10)
var globalFlags []cli.Flag = make([]cli.Flag, 0, 10)
// //
// If the executable is forgejo-cli, provide a Forgejo specific CLI // If the executable is forgejo-cli, provide a Forgejo specific CLI
@ -131,6 +132,15 @@ func NewMainApp(version, versionExtra string) *cli.App {
// //
if executable == "forgejo-cli" { if executable == "forgejo-cli" {
subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdActions(context.Background())) subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdActions(context.Background()))
subCmdWithConfig = append(subCmdWithConfig, forgejo.CmdF3(context.Background()))
globalFlags = append(globalFlags, []cli.Flag{
&cli.BoolFlag{
Name: "quiet",
},
&cli.BoolFlag{
Name: "verbose",
},
}...)
} else { } else {
// //
// Otherwise provide a Gitea compatible CLI which includes Forgejo // Otherwise provide a Gitea compatible CLI which includes Forgejo
@ -142,10 +152,10 @@ func NewMainApp(version, versionExtra string) *cli.App {
subCmdWithConfig = append(subCmdWithConfig, CmdActions) subCmdWithConfig = append(subCmdWithConfig, CmdActions)
} }
return innerNewMainApp(version, versionExtra, subCmdsStandalone, subCmdWithConfig) return innerNewMainApp(version, versionExtra, subCmdsStandalone, subCmdWithConfig, globalFlags)
} }
func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command) *cli.App { func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs []cli.Flag) *cli.App {
app := cli.NewApp() app := cli.NewApp()
app.HelpName = "forgejo" app.HelpName = "forgejo"
app.Name = "Forgejo" app.Name = "Forgejo"
@ -185,6 +195,7 @@ func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmd
app.DefaultCommand = CmdWeb.Name app.DefaultCommand = CmdWeb.Name
globalFlags := appGlobalFlags() globalFlags := appGlobalFlags()
globalFlags = append(globalFlags, globalFlagsArgs...)
app.Flags = append(app.Flags, cli.VersionFlag) app.Flags = append(app.Flags, cli.VersionFlag)
app.Flags = append(app.Flags, globalFlags...) app.Flags = append(app.Flags, globalFlags...)
app.HideHelp = true // use our own help action to show helps (with more information like default config) app.HideHelp = true // use our own help action to show helps (with more information like default config)

View file

@ -2499,6 +2499,15 @@ LEVEL = Info
;; If set to true, completely ignores server certificate validation errors. This option is unsafe. ;; If set to true, completely ignores server certificate validation errors. This option is unsafe.
;SKIP_TLS_VERIFY = false ;SKIP_TLS_VERIFY = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[F3]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Enable/Disable Friendly Forge Format (F3)
;ENABLED = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[federation] ;[federation]

2
go.mod
View file

@ -5,6 +5,7 @@ go 1.22.0
toolchain go1.22.4 toolchain go1.22.4
require ( require (
code.forgejo.org/f3/gof3/v3 v3.3.1
code.forgejo.org/forgejo/reply v1.0.2 code.forgejo.org/forgejo/reply v1.0.2
code.gitea.io/actions-proto-go v0.4.0 code.gitea.io/actions-proto-go v0.4.0
code.gitea.io/gitea-vet v0.2.3 code.gitea.io/gitea-vet v0.2.3
@ -202,6 +203,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.0 // indirect github.com/google/go-tpm v0.9.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 // indirect github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 // indirect

2
go.sum
View file

@ -1,5 +1,7 @@
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
code.forgejo.org/f3/gof3/v3 v3.3.1 h1:lG7vT1Y/OIJDcZJE+mm5uycLyOQm0nVwGxnIWcBQ5XY=
code.forgejo.org/f3/gof3/v3 v3.3.1/go.mod h1:Wk0xt06c72MPbEplc3TZYYOTTHDlCR15YpgFG0ggci8=
code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEjb3jaYYtmSE= code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEjb3jaYYtmSE=
code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ= code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=

22
modules/setting/f3.go Normal file
View file

@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
package setting
import (
"code.gitea.io/gitea/modules/log"
)
// Friendly Forge Format (F3) settings
var (
F3 = struct {
Enabled bool
}{
Enabled: false,
}
)
func loadF3From(rootCfg ConfigProvider) {
if err := rootCfg.Section("F3").MapTo(&F3); err != nil {
log.Fatal("Failed to map F3 settings: %v", err)
}
}

View file

@ -224,6 +224,7 @@ func LoadSettings() {
loadProjectFrom(CfgProvider) loadProjectFrom(CfgProvider)
loadMimeTypeMapFrom(CfgProvider) loadMimeTypeMapFrom(CfgProvider)
loadFederationFrom(CfgProvider) loadFederationFrom(CfgProvider)
loadF3From(CfgProvider)
} }
// LoadSettingsForInstall initializes the settings for install // LoadSettingsForInstall initializes the settings for install

171
services/f3/driver/asset.go Normal file
View file

@ -0,0 +1,171 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/services/attachment"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
"github.com/google/uuid"
)
var _ f3_tree.ForgeDriverInterface = &issue{}
type asset struct {
common
forgejoAsset *repo_model.Attachment
sha string
contentType string
downloadFunc f3.DownloadFuncType
}
func (o *asset) SetNative(asset any) {
o.forgejoAsset = asset.(*repo_model.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()
}
return &f3.ReleaseAsset{
Common: f3.NewCommon(o.GetNativeID()),
Name: o.forgejoAsset.Name,
ContentType: o.contentType,
Size: o.forgejoAsset.Size,
DownloadCount: o.forgejoAsset.DownloadCount,
Created: o.forgejoAsset.CreatedUnix.AsTime(),
SHA256: o.sha,
DownloadURL: o.forgejoAsset.DownloadURL(),
DownloadFunc: o.downloadFunc,
}
}
func (o *asset) FromFormat(content f3.Interface) {
asset := content.(*f3.ReleaseAsset)
o.forgejoAsset = &repo_model.Attachment{
ID: f3_util.ParseInt(asset.GetID()),
Name: asset.Name,
Size: asset.Size,
DownloadCount: asset.DownloadCount,
CreatedUnix: timeutil.TimeStamp(asset.Created.Unix()),
CustomDownloadURL: asset.DownloadURL,
}
o.contentType = asset.ContentType
o.sha = asset.SHA256
o.downloadFunc = asset.DownloadFunc
}
func (o *asset) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
id := f3_util.ParseInt(string(node.GetID()))
asset, err := repo_model.GetAttachmentByID(ctx, id)
if repo_model.IsErrAttachmentNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("asset %v %w", id, err))
}
o.forgejoAsset = asset
path := o.forgejoAsset.RelativePath()
{
f, err := storage.Attachments.Open(path)
if err != nil {
panic(err)
}
hasher := sha256.New()
if _, err := io.Copy(hasher, f); err != nil {
panic(fmt.Errorf("io.Copy to hasher: %v", err))
}
o.sha = hex.EncodeToString(hasher.Sum(nil))
}
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 true
}
func (o *asset) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoAsset.ID)
if _, err := db.GetEngine(ctx).ID(o.forgejoAsset.ID).Cols("name").Update(o.forgejoAsset); err != nil {
panic(fmt.Errorf("UpdateAssetCols: %v %v", o.forgejoAsset, err))
}
}
func (o *asset) Put(ctx context.Context) generic.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
uploader, err := user_model.GetAdminUser(ctx)
if err != nil {
panic(fmt.Errorf("GetAdminUser %w", err))
}
o.forgejoAsset.UploaderID = uploader.ID
o.forgejoAsset.RepoID = f3_tree.GetProjectID(o.GetNode())
o.forgejoAsset.ReleaseID = f3_tree.GetReleaseID(o.GetNode())
o.forgejoAsset.UUID = uuid.New().String()
download := o.downloadFunc()
defer download.Close()
_, err = attachment.NewAttachment(ctx, o.forgejoAsset, download, o.forgejoAsset.Size)
if err != nil {
panic(err)
}
o.Trace("asset created %d", o.forgejoAsset.ID)
return generic.NodeID(fmt.Sprintf("%d", o.forgejoAsset.ID))
}
func (o *asset) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
if err := repo_model.DeleteAttachment(ctx, o.forgejoAsset, true); err != nil {
panic(err)
}
}
func newAsset() generic.NodeDriverInterface {
return &asset{}
}

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 driver
import (
"context"
"fmt"
repo_model "code.gitea.io/gitea/models/repo"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type assets struct {
container
}
func (o *assets) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
if page > 1 {
return generic.NewChildrenSlice(0)
}
releaseID := f3_tree.GetReleaseID(o.GetNode())
release, err := repo_model.GetReleaseByID(ctx, releaseID)
if err != nil {
panic(fmt.Errorf("GetReleaseByID %v %w", releaseID, err))
}
if err := release.LoadAttributes(ctx); err != nil {
panic(fmt.Errorf("error while listing assets: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(release.Attachments...)...)
}
func newAssets() generic.NodeDriverInterface {
return &assets{}
}

View file

@ -0,0 +1,122 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &comment{}
type comment struct {
common
forgejoComment *issues_model.Comment
}
func (o *comment) SetNative(comment any) {
o.forgejoComment = comment.(*issues_model.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(fmt.Sprintf("%d", o.forgejoComment.ID)),
PosterID: f3_tree.NewUserReference(o.forgejoComment.Poster.ID),
Content: o.forgejoComment.Content,
Created: o.forgejoComment.CreatedUnix.AsTime(),
Updated: o.forgejoComment.UpdatedUnix.AsTime(),
}
}
func (o *comment) FromFormat(content f3.Interface) {
comment := content.(*f3.Comment)
o.forgejoComment = &issues_model.Comment{
ID: f3_util.ParseInt(comment.GetID()),
PosterID: comment.PosterID.GetIDAsInt(),
Poster: &user_model.User{
ID: comment.PosterID.GetIDAsInt(),
},
Content: comment.Content,
CreatedUnix: timeutil.TimeStamp(comment.Created.Unix()),
UpdatedUnix: timeutil.TimeStamp(comment.Updated.Unix()),
}
}
func (o *comment) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
id := f3_util.ParseInt(string(node.GetID()))
comment, err := issues_model.GetCommentByID(ctx, id)
if issues_model.IsErrCommentNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("comment %v %w", id, err))
}
if err := comment.LoadPoster(ctx); err != nil {
panic(fmt.Errorf("LoadPoster %v %w", *comment, err))
}
o.forgejoComment = comment
return true
}
func (o *comment) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoComment.ID)
if _, err := db.GetEngine(ctx).ID(o.forgejoComment.ID).Cols("content").Update(o.forgejoComment); err != nil {
panic(fmt.Errorf("UpdateCommentCols: %v %v", o.forgejoComment, err))
}
}
func (o *comment) Put(ctx context.Context) generic.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
sess := db.GetEngine(ctx)
if _, err := sess.NoAutoTime().Insert(o.forgejoComment); err != nil {
panic(err)
}
o.Trace("comment created %d", o.forgejoComment.ID)
return generic.NodeID(fmt.Sprintf("%d", o.forgejoComment.ID))
}
func (o *comment) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
if err := issues_model.DeleteComment(ctx, o.forgejoComment); err != nil {
panic(err)
}
}
func newComment() generic.NodeDriverInterface {
return &comment{}
}

View file

@ -0,0 +1,49 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type comments struct {
container
}
func (o *comments) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
project := f3_tree.GetProjectID(o.GetNode())
commentable := f3_tree.GetCommentableID(o.GetNode())
issue, err := issues_model.GetIssueByIndex(ctx, project, commentable)
if err != nil {
panic(fmt.Errorf("GetIssueByIndex %v %w", commentable, err))
}
sess := db.GetEngine(ctx).
Table("comment").
Where("`issue_id` = ? AND `type` = ?", issue.ID, issues_model.CommentTypeComment)
if page != 0 {
sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: pageSize})
}
forgejoComments := make([]*issues_model.Comment, 0, pageSize)
if err := sess.Find(&forgejoComments); 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{}
}

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 driver
import (
"context"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
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) GetNativeID() string {
return ""
}
func (o *common) SetNative(native any) {
}
func (o *common) getTree() generic.TreeInterface {
return o.GetNode().GetTree()
}
func (o *common) getPageSize() int {
return o.getTreeDriver().GetPageSize()
}
func (o *common) getKind() generic.Kind {
return o.GetNode().GetKind()
}
func (o *common) getTreeDriver() *treeDriver {
return o.GetTreeDriver().(*treeDriver)
}
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 driver
import (
"context"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
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) generic.NodeID {
return o.upsert(ctx)
}
func (o *container) Patch(ctx context.Context) {
o.upsert(ctx)
}
func (o *container) upsert(context.Context) generic.NodeID {
return generic.NodeID(o.getKind())
}

View file

@ -0,0 +1,64 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
user_model "code.gitea.io/gitea/models/user"
"code.forgejo.org/f3/gof3/v3/f3"
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 forge struct {
generic.NullDriver
ownersKind map[string]generic.Kind
}
func newForge() generic.NodeDriverInterface {
return &forge{
ownersKind: make(map[string]generic.Kind),
}
}
func (o *forge) getOwnersKind(ctx context.Context, id string) generic.Kind {
kind, ok := o.ownersKind[id]
if !ok {
user, err := user_model.GetUserByID(ctx, util.ParseInt(id))
if err != nil {
panic(fmt.Errorf("user_repo.GetUserByID: %w", err))
}
kind = f3_tree.KindUsers
if user.IsOrganization() {
kind = f3_tree.KindOrganization
}
o.ownersKind[id] = kind
}
return kind
}
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) generic.NodeID { return generic.NodeID("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(),
}
}

238
services/f3/driver/issue.go Normal file
View file

@ -0,0 +1,238 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/timeutil"
issue_service "code.gitea.io/gitea/services/issue"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &issue{}
type issue struct {
common
forgejoIssue *issues_model.Issue
}
func (o *issue) SetNative(issue any) {
o.forgejoIssue = issue.(*issues_model.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()
}
var 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.Content,
Milestone: milestone,
State: string(o.forgejoIssue.State()),
Created: o.forgejoIssue.CreatedUnix.AsTime(),
Updated: o.forgejoIssue.UpdatedUnix.AsTime(),
Closed: o.forgejoIssue.ClosedUnix.AsTimePtr(),
IsLocked: o.forgejoIssue.IsLocked,
}
}
func (o *issue) FromFormat(content f3.Interface) {
issue := content.(*f3.Issue)
var milestone *issues_model.Milestone
if issue.Milestone != nil {
milestone = &issues_model.Milestone{
ID: issue.Milestone.GetIDAsInt(),
}
}
o.forgejoIssue = &issues_model.Issue{
Title: issue.Title,
Index: f3_util.ParseInt(issue.GetID()),
PosterID: issue.PosterID.GetIDAsInt(),
Poster: &user_model.User{
ID: issue.PosterID.GetIDAsInt(),
},
Content: issue.Content,
Milestone: milestone,
IsClosed: issue.State == "closed",
CreatedUnix: timeutil.TimeStamp(issue.Created.Unix()),
UpdatedUnix: timeutil.TimeStamp(issue.Updated.Unix()),
IsLocked: issue.IsLocked,
}
assignees := make([]*user_model.User, 0, len(issue.Assignees))
for _, assignee := range issue.Assignees {
assignees = append(assignees, &user_model.User{ID: assignee.GetIDAsInt()})
}
o.forgejoIssue.Assignees = assignees
labels := make([]*issues_model.Label, 0, len(issue.Labels))
for _, label := range issue.Labels {
labels = append(labels, &issues_model.Label{ID: label.GetIDAsInt()})
}
o.forgejoIssue.Labels = labels
if issue.Closed != nil {
o.forgejoIssue.ClosedUnix = timeutil.TimeStamp(issue.Closed.Unix())
}
}
func (o *issue) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
project := f3_tree.GetProjectID(o.GetNode())
id := f3_util.ParseInt(string(node.GetID()))
issue, err := issues_model.GetIssueByIndex(ctx, project, id)
if issues_model.IsErrIssueNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("issue %v %w", id, err))
}
if err := issue.LoadAttributes(ctx); err != nil {
panic(err)
}
o.forgejoIssue = issue
return true
}
func (o *issue) Patch(ctx context.Context) {
node := o.GetNode()
project := f3_tree.GetProjectID(o.GetNode())
id := f3_util.ParseInt(string(node.GetID()))
o.Trace("repo_id = %d, index = %d", project, id)
if _, err := db.GetEngine(ctx).Where("`repo_id` = ? AND `index` = ?", project, id).Cols("name", "content").Update(o.forgejoIssue); err != nil {
panic(fmt.Errorf("%v %v", o.forgejoIssue, err))
}
}
func (o *issue) Put(ctx context.Context) generic.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
o.forgejoIssue.RepoID = f3_tree.GetProjectID(o.GetNode())
makeLabels := func(issueID int64) []issues_model.IssueLabel {
labels := make([]issues_model.IssueLabel, 0, len(o.forgejoIssue.Labels))
for _, label := range o.forgejoIssue.Labels {
o.Trace("%d with label %d", issueID, label.ID)
labels = append(labels, issues_model.IssueLabel{
IssueID: issueID,
LabelID: label.ID,
})
}
return labels
}
idx, err := db.GetNextResourceIndex(ctx, "issue_index", o.forgejoIssue.RepoID)
if err != nil {
panic(fmt.Errorf("generate issue index failed: %w", err))
}
o.forgejoIssue.Index = idx
sess := db.GetEngine(ctx)
if _, err = sess.NoAutoTime().Insert(o.forgejoIssue); err != nil {
panic(err)
}
labels := makeLabels(o.forgejoIssue.ID)
if len(labels) > 0 {
if _, err := sess.Insert(labels); err != nil {
panic(err)
}
}
makeAssignees := func(issueID int64) []issues_model.IssueAssignees {
assignees := make([]issues_model.IssueAssignees, 0, len(o.forgejoIssue.Assignees))
for _, assignee := range o.forgejoIssue.Assignees {
o.Trace("%d with assignee %d", issueID, assignee.ID)
assignees = append(assignees, issues_model.IssueAssignees{
IssueID: issueID,
AssigneeID: assignee.ID,
})
}
return assignees
}
assignees := makeAssignees(o.forgejoIssue.ID)
if len(assignees) > 0 {
if _, err := sess.Insert(assignees); err != nil {
panic(err)
}
}
o.Trace("issue created %d/%d", o.forgejoIssue.ID, o.forgejoIssue.Index)
return generic.NodeID(fmt.Sprintf("%d", o.forgejoIssue.Index))
}
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())
repoPath := repo_model.RepoPath(owner, project)
gitRepo, err := git.OpenRepository(ctx, repoPath)
if err != nil {
panic(err)
}
defer gitRepo.Close()
doer, err := user_model.GetAdminUser(ctx)
if err != nil {
panic(fmt.Errorf("GetAdminUser %w", err))
}
if err := issue_service.DeleteIssue(ctx, doer, gitRepo, o.forgejoIssue); err != nil {
panic(err)
}
}
func newIssue() generic.NodeDriverInterface {
return &issue{}
}

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 driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type issues struct {
container
}
func (o *issues) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
project := f3_tree.GetProjectID(o.GetNode())
forgejoIssues, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
Paginator: &db.ListOptions{Page: page, PageSize: pageSize},
RepoIDs: []int64{project},
})
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{}
}

113
services/f3/driver/label.go Normal file
View file

@ -0,0 +1,113 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &label{}
type label struct {
common
forgejoLabel *issues_model.Label
}
func (o *label) SetNative(label any) {
o.forgejoLabel = label.(*issues_model.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(fmt.Sprintf("%d", o.forgejoLabel.ID)),
Name: o.forgejoLabel.Name,
Color: o.forgejoLabel.Color,
Description: o.forgejoLabel.Description,
}
}
func (o *label) FromFormat(content f3.Interface) {
label := content.(*f3.Label)
o.forgejoLabel = &issues_model.Label{
ID: f3_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())
project := f3_tree.GetProjectID(o.GetNode())
id := f3_util.ParseInt(string(node.GetID()))
label, err := issues_model.GetLabelInRepoByID(ctx, project, id)
if issues_model.IsErrRepoLabelNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("label %v %w", id, err))
}
o.forgejoLabel = label
return true
}
func (o *label) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoLabel.ID)
if _, err := db.GetEngine(ctx).ID(o.forgejoLabel.ID).Cols("name", "description").Update(o.forgejoLabel); err != nil {
panic(fmt.Errorf("UpdateLabelCols: %v %v", o.forgejoLabel, err))
}
}
func (o *label) Put(ctx context.Context) generic.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
o.forgejoLabel.RepoID = f3_tree.GetProjectID(o.GetNode())
if err := issues_model.NewLabel(ctx, o.forgejoLabel); err != nil {
panic(err)
}
o.Trace("label created %d", o.forgejoLabel.ID)
return generic.NodeID(fmt.Sprintf("%d", o.forgejoLabel.ID))
}
func (o *label) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
project := f3_tree.GetProjectID(o.GetNode())
if err := issues_model.DeleteLabel(ctx, project, o.forgejoLabel.ID); err != nil {
panic(err)
}
}
func newLabel() generic.NodeDriverInterface {
return &label{}
}

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 driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type labels struct {
container
}
func (o *labels) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
project := f3_tree.GetProjectID(o.GetNode())
forgejoLabels, err := issues_model.GetLabelsByRepoID(ctx, project, "", db.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{}
}

View file

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

View file

@ -0,0 +1,30 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"testing"
"code.gitea.io/gitea/models/unittest"
driver_options "code.gitea.io/gitea/services/f3/driver/options"
_ "code.gitea.io/gitea/models"
_ "code.gitea.io/gitea/models/actions"
_ "code.gitea.io/gitea/models/activities"
_ "code.gitea.io/gitea/models/perm/access"
_ "code.gitea.io/gitea/services/f3/driver/tests"
tests_f3 "code.forgejo.org/f3/gof3/v3/tree/tests/f3"
"github.com/stretchr/testify/assert"
)
func TestF3(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
tests_f3.ForgeCompliance(t, driver_options.Name)
}
func TestMain(m *testing.M) {
unittest.MainTest(m)
}

View file

@ -0,0 +1,150 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"time"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &milestone{}
type milestone struct {
common
forgejoMilestone *issues_model.Milestone
}
func (o *milestone) SetNative(milestone any) {
o.forgejoMilestone = milestone.(*issues_model.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()
}
return &f3.Milestone{
Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoMilestone.ID)),
Title: o.forgejoMilestone.Name,
Description: o.forgejoMilestone.Content,
Created: o.forgejoMilestone.CreatedUnix.AsTime(),
Updated: o.forgejoMilestone.UpdatedUnix.AsTimePtr(),
Deadline: o.forgejoMilestone.DeadlineUnix.AsTimePtr(),
State: string(o.forgejoMilestone.State()),
}
}
func (o *milestone) FromFormat(content f3.Interface) {
milestone := content.(*f3.Milestone)
var deadline timeutil.TimeStamp
if milestone.Deadline != nil {
deadline = timeutil.TimeStamp(milestone.Deadline.Unix())
}
if deadline == 0 {
deadline = timeutil.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.DefaultUILocation).Unix())
}
var closed timeutil.TimeStamp
if milestone.Closed != nil {
closed = timeutil.TimeStamp(milestone.Closed.Unix())
}
if milestone.Created.IsZero() {
if milestone.Updated != nil {
milestone.Created = *milestone.Updated
} else if milestone.Deadline != nil {
milestone.Created = *milestone.Deadline
} else {
milestone.Created = time.Now()
}
}
if milestone.Updated == nil || milestone.Updated.IsZero() {
milestone.Updated = &milestone.Created
}
o.forgejoMilestone = &issues_model.Milestone{
ID: f3_util.ParseInt(milestone.GetID()),
Name: milestone.Title,
Content: milestone.Description,
IsClosed: milestone.State == "closed",
CreatedUnix: timeutil.TimeStamp(milestone.Created.Unix()),
UpdatedUnix: timeutil.TimeStamp(milestone.Updated.Unix()),
ClosedDateUnix: closed,
DeadlineUnix: deadline,
}
}
func (o *milestone) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
project := f3_tree.GetProjectID(o.GetNode())
id := f3_util.ParseInt(string(node.GetID()))
milestone, err := issues_model.GetMilestoneByRepoID(ctx, project, id)
if issues_model.IsErrMilestoneNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("milestone %v %w", id, err))
}
o.forgejoMilestone = milestone
return true
}
func (o *milestone) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoMilestone.ID)
if _, err := db.GetEngine(ctx).ID(o.forgejoMilestone.ID).Cols("name", "description").Update(o.forgejoMilestone); err != nil {
panic(fmt.Errorf("UpdateMilestoneCols: %v %v", o.forgejoMilestone, err))
}
}
func (o *milestone) Put(ctx context.Context) generic.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
o.forgejoMilestone.RepoID = f3_tree.GetProjectID(o.GetNode())
if err := issues_model.NewMilestone(ctx, o.forgejoMilestone); err != nil {
panic(err)
}
o.Trace("milestone created %d", o.forgejoMilestone.ID)
return generic.NodeID(fmt.Sprintf("%d", o.forgejoMilestone.ID))
}
func (o *milestone) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
project := f3_tree.GetProjectID(o.GetNode())
if err := issues_model.DeleteMilestoneByRepoID(ctx, project, o.forgejoMilestone.ID); err != nil {
panic(err)
}
}
func newMilestone() generic.NodeDriverInterface {
return &milestone{}
}

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 driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type milestones struct {
container
}
func (o *milestones) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
project := f3_tree.GetProjectID(o.GetNode())
forgejoMilestones, err := db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
ListOptions: db.ListOptions{Page: page, PageSize: pageSize},
RepoID: project,
})
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{}
}

View file

@ -0,0 +1,20 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"net/http"
driver_options "code.gitea.io/gitea/services/f3/driver/options"
"code.forgejo.org/f3/gof3/v3/options"
)
func newOptions() options.Interface {
o := &driver_options.Options{}
o.SetName(driver_options.Name)
o.SetNewMigrationHTTPClient(func() *http.Client { return &http.Client{} })
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 = "internal_forgejo"

View file

@ -0,0 +1,31 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package options
import (
"net/http"
"code.forgejo.org/f3/gof3/v3/options"
"code.forgejo.org/f3/gof3/v3/options/cli"
"code.forgejo.org/f3/gof3/v3/options/logger"
)
type NewMigrationHTTPClientFun func() *http.Client
type Options struct {
options.Options
logger.OptionsLogger
cli.OptionsCLI
NewMigrationHTTPClient NewMigrationHTTPClientFun
}
func (o *Options) GetNewMigrationHTTPClient() NewMigrationHTTPClientFun {
return o.NewMigrationHTTPClient
}
func (o *Options) SetNewMigrationHTTPClient(fun NewMigrationHTTPClientFun) {
o.NewMigrationHTTPClient = fun
}

View file

@ -0,0 +1,111 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
org_model "code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &organization{}
type organization struct {
common
forgejoOrganization *org_model.Organization
}
func (o *organization) SetNative(organization any) {
o.forgejoOrganization = organization.(*org_model.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.Name,
FullName: o.forgejoOrganization.FullName,
}
}
func (o *organization) FromFormat(content f3.Interface) {
organization := content.(*f3.Organization)
o.forgejoOrganization = &org_model.Organization{
ID: f3_util.ParseInt(organization.GetID()),
Name: organization.Name,
FullName: organization.FullName,
}
}
func (o *organization) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
id := f3_util.ParseInt(string(node.GetID()))
organization, err := org_model.GetOrgByID(ctx, id)
if user_model.IsErrUserNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("organization %v %w", id, err))
}
o.forgejoOrganization = organization
return true
}
func (o *organization) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoOrganization.ID)
if _, err := db.GetEngine(ctx).ID(o.forgejoOrganization.ID).Cols("full_name").Update(o.forgejoOrganization); err != nil {
panic(fmt.Errorf("UpdateOrganizationCols: %v %v", o.forgejoOrganization, err))
}
}
func (o *organization) Put(ctx context.Context) generic.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
doer, err := user_model.GetAdminUser(ctx)
if err != nil {
panic(fmt.Errorf("GetAdminUser %w", err))
}
err = org_model.CreateOrganization(ctx, o.forgejoOrganization, doer)
if err != nil {
panic(err)
}
return generic.NodeID(fmt.Sprintf("%d", o.forgejoOrganization.ID))
}
func (o *organization) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
if err := org_model.DeleteOrganization(ctx, o.forgejoOrganization); err != nil {
panic(err)
}
}
func newOrganization() generic.NodeDriverInterface {
return &organization{}
}

View file

@ -0,0 +1,50 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
org_model "code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type organizations struct {
container
}
func (o *organizations) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
sess := db.GetEngine(ctx)
if page != 0 {
sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: o.getPageSize()})
}
sess = sess.Select("`user`.*").
Where("`type`=?", user_model.UserTypeOrganization)
organizations := make([]*org_model.Organization, 0, o.getPageSize())
if err := sess.Find(&organizations); err != nil {
panic(fmt.Errorf("error while listing organizations: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(organizations...)...)
}
func (o *organizations) GetIDFromName(ctx context.Context, name string) generic.NodeID {
organization, err := org_model.GetOrgByName(ctx, name)
if err != nil {
panic(fmt.Errorf("GetOrganizationByName: %v", err))
}
return generic.NodeID(fmt.Sprintf("%d", organization.ID))
}
func newOrganizations() generic.NodeDriverInterface {
return &organizations{}
}

View file

@ -0,0 +1,188 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"strings"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
repo_service "code.gitea.io/gitea/services/repository"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &project{}
type project struct {
common
forgejoProject *repo_model.Repository
forked *f3.Reference
}
func (o *project) SetNative(project any) {
o.forgejoProject = project.(*repo_model.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) setForkedReference(ctx context.Context) {
if !o.forgejoProject.IsFork {
return
}
if err := o.forgejoProject.GetBaseRepo(ctx); err != nil {
panic(fmt.Errorf("GetBaseRepo %v %w", o.forgejoProject, err))
}
forkParent := o.forgejoProject.BaseRepo
if err := forkParent.LoadOwner(ctx); err != nil {
panic(fmt.Errorf("LoadOwner %v %w", forkParent, err))
}
owners := "users"
if forkParent.Owner.IsOrganization() {
owners = "organizations"
}
o.forked = f3_tree.NewProjectReference(owners, fmt.Sprintf("%d", forkParent.Owner.ID), fmt.Sprintf("%d", forkParent.ID))
}
func (o *project) ToFormat() f3.Interface {
if o.forgejoProject == nil {
return o.NewFormat()
}
return &f3.Project{
Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoProject.ID)),
Name: o.forgejoProject.Name,
IsPrivate: o.forgejoProject.IsPrivate,
IsMirror: o.forgejoProject.IsMirror,
Description: o.forgejoProject.Description,
DefaultBranch: o.forgejoProject.DefaultBranch,
Forked: o.forked,
}
}
func (o *project) FromFormat(content f3.Interface) {
project := content.(*f3.Project)
o.forgejoProject = &repo_model.Repository{
ID: f3_util.ParseInt(project.GetID()),
Name: project.Name,
IsPrivate: project.IsPrivate,
IsMirror: project.IsMirror,
Description: project.Description,
DefaultBranch: project.DefaultBranch,
}
if project.Forked != nil {
o.forgejoProject.IsFork = true
o.forgejoProject.ForkID = project.Forked.GetIDAsInt()
}
o.forked = project.Forked
}
func (o *project) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
id := f3_util.ParseInt(string(node.GetID()))
u, err := repo_model.GetRepositoryByID(ctx, id)
if repo_model.IsErrRepoNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("project %v %w", id, err))
}
o.forgejoProject = u
o.setForkedReference(ctx)
return true
}
func (o *project) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoProject.ID)
o.forgejoProject.LowerName = strings.ToLower(o.forgejoProject.Name)
if err := repo_model.UpdateRepositoryCols(ctx, o.forgejoProject,
"description",
"name",
"lower_name",
); err != nil {
panic(fmt.Errorf("UpdateRepositoryCols: %v %v", o.forgejoProject, err))
}
}
func (o *project) Put(ctx context.Context) generic.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
ownerID := f3_tree.GetOwnerID(o.GetNode())
owner, err := user_model.GetUserByID(ctx, ownerID)
if err != nil {
panic(fmt.Errorf("GetUserByID %v %w", ownerID, err))
}
doer, err := user_model.GetAdminUser(ctx)
if err != nil {
panic(fmt.Errorf("GetAdminUser %w", err))
}
if o.forked == nil {
repo, err := repo_service.CreateRepositoryDirectly(ctx, doer, owner, repo_service.CreateRepoOptions{
Name: o.forgejoProject.Name,
Description: o.forgejoProject.Description,
IsPrivate: o.forgejoProject.IsPrivate,
DefaultBranch: o.forgejoProject.DefaultBranch,
})
if err != nil {
panic(err)
}
o.forgejoProject = repo
o.Trace("project created %d", o.forgejoProject.ID)
} else {
if err = o.forgejoProject.GetBaseRepo(ctx); err != nil {
panic(fmt.Errorf("GetBaseRepo %v %w", o.forgejoProject, err))
}
if err = o.forgejoProject.BaseRepo.LoadOwner(ctx); err != nil {
panic(fmt.Errorf("LoadOwner %v %w", o.forgejoProject.BaseRepo, err))
}
repo, err := repo_service.ForkRepository(ctx, doer, owner, repo_service.ForkRepoOptions{
BaseRepo: o.forgejoProject.BaseRepo,
Name: o.forgejoProject.Name,
Description: o.forgejoProject.Description,
})
if err != nil {
panic(err)
}
o.forgejoProject = repo
o.Trace("project created %d", o.forgejoProject.ID)
}
return generic.NodeID(fmt.Sprintf("%d", o.forgejoProject.ID))
}
func (o *project) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
doer, err := user_model.GetAdminUser(ctx)
if err != nil {
panic(fmt.Errorf("GetAdminUser %w", err))
}
if err := repo_service.DeleteRepository(ctx, doer, o.forgejoProject, true); err != nil {
panic(err)
}
}
func newProject() generic.NodeDriverInterface {
return &project{}
}

View file

@ -0,0 +1,56 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
type projects struct {
container
}
func (o *projects) GetIDFromName(ctx context.Context, name string) generic.NodeID {
owner := f3_tree.GetOwnerName(o.GetNode())
forgejoProject, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, name)
if repo_model.IsErrRepoNotExist(err) {
return generic.NilID
}
if err != nil {
panic(fmt.Errorf("error GetRepositoryByOwnerAndName(%s, %s): %v", owner, name, err))
}
return generic.NodeID(fmt.Sprintf("%d", forgejoProject.ID))
}
func (o *projects) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
owner := f3_tree.GetOwner(o.GetNode())
forgejoProjects, _, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{Page: page, PageSize: pageSize},
OwnerID: f3_util.ParseInt(string(owner.GetID())),
Private: true,
})
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,320 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"time"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/timeutil"
issue_service "code.gitea.io/gitea/services/issue"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &pullRequest{}
type pullRequest struct {
common
forgejoPullRequest *issues_model.Issue
headRepository *f3.Reference
baseRepository *f3.Reference
fetchFunc f3.PullRequestFetchFunc
}
func (o *pullRequest) SetNative(pullRequest any) {
o.forgejoPullRequest = pullRequest.(*issues_model.Issue)
}
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 *repo_model.Repository) *f3.Reference {
if repository == nil {
panic("unexpected nil repository")
}
forge := o.getTree().GetRoot().GetChild(f3_tree.KindForge).GetDriver().(*forge)
owners := forge.getOwnersPath(ctx, fmt.Sprintf("%d", repository.OwnerID))
return f3_tree.NewRepositoryReference(owners.String(), repository.OwnerID, repository.ID)
}
func (o *pullRequest) referenceToRepository(reference *f3.Reference) int64 {
var project int64
if reference.Get() == "../../repository/vcs" {
project = f3_tree.GetProjectID(o.GetNode())
} else {
p := f3_tree.ToPath(generic.PathAbsolute(o.GetNode().GetCurrentPath().String(), reference.Get()))
o.Trace("%v %v", o.GetNode().GetCurrentPath().String(), p)
_, project = p.OwnerAndProjectID()
}
return project
}
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)
}
var mergedTime *time.Time
if o.forgejoPullRequest.PullRequest.HasMerged {
mergedTime = o.forgejoPullRequest.PullRequest.MergedUnix.AsTimePtr()
}
var closedTime *time.Time
if o.forgejoPullRequest.IsClosed {
closedTime = o.forgejoPullRequest.ClosedUnix.AsTimePtr()
}
makePullRequestBranch := func(repo *repo_model.Repository, branch string) f3.PullRequestBranch {
r, err := git.OpenRepository(context.Background(), repo.RepoPath())
if err != nil {
panic(err)
}
defer r.Close()
b, err := r.GetBranch(branch)
if err != nil {
panic(err)
}
c, err := b.GetCommit()
if err != nil {
panic(err)
}
return f3.PullRequestBranch{
Ref: branch,
SHA: c.ID.String(),
}
}
if err := o.forgejoPullRequest.PullRequest.LoadHeadRepo(db.DefaultContext); err != nil {
panic(err)
}
head := makePullRequestBranch(o.forgejoPullRequest.PullRequest.HeadRepo, o.forgejoPullRequest.PullRequest.HeadBranch)
head.Repository = o.headRepository
if err := o.forgejoPullRequest.PullRequest.LoadBaseRepo(db.DefaultContext); err != nil {
panic(err)
}
base := makePullRequestBranch(o.forgejoPullRequest.PullRequest.BaseRepo, o.forgejoPullRequest.PullRequest.BaseBranch)
base.Repository = o.baseRepository
return &f3.PullRequest{
Common: f3.NewCommon(o.GetNativeID()),
PosterID: f3_tree.NewUserReference(o.forgejoPullRequest.Poster.ID),
Title: o.forgejoPullRequest.Title,
Content: o.forgejoPullRequest.Content,
Milestone: milestone,
State: string(o.forgejoPullRequest.State()),
IsLocked: o.forgejoPullRequest.IsLocked,
Created: o.forgejoPullRequest.CreatedUnix.AsTime(),
Updated: o.forgejoPullRequest.UpdatedUnix.AsTime(),
Closed: closedTime,
Merged: o.forgejoPullRequest.PullRequest.HasMerged,
MergedTime: mergedTime,
MergeCommitSHA: o.forgejoPullRequest.PullRequest.MergedCommitID,
Head: head,
Base: base,
FetchFunc: o.fetchFunc,
}
}
func (o *pullRequest) FromFormat(content f3.Interface) {
pullRequest := content.(*f3.PullRequest)
var milestone *issues_model.Milestone
if pullRequest.Milestone != nil {
milestone = &issues_model.Milestone{
ID: pullRequest.Milestone.GetIDAsInt(),
}
}
o.headRepository = pullRequest.Head.Repository
o.baseRepository = pullRequest.Base.Repository
pr := issues_model.PullRequest{
HeadBranch: pullRequest.Head.Ref,
HeadRepoID: o.referenceToRepository(o.headRepository),
BaseBranch: pullRequest.Base.Ref,
BaseRepoID: o.referenceToRepository(o.baseRepository),
MergeBase: pullRequest.Base.SHA,
Index: f3_util.ParseInt(pullRequest.GetID()),
HasMerged: pullRequest.Merged,
}
o.forgejoPullRequest = &issues_model.Issue{
Index: f3_util.ParseInt(pullRequest.GetID()),
PosterID: pullRequest.PosterID.GetIDAsInt(),
Poster: &user_model.User{
ID: pullRequest.PosterID.GetIDAsInt(),
},
Title: pullRequest.Title,
Content: pullRequest.Content,
Milestone: milestone,
IsClosed: pullRequest.State == "closed",
CreatedUnix: timeutil.TimeStamp(pullRequest.Created.Unix()),
UpdatedUnix: timeutil.TimeStamp(pullRequest.Updated.Unix()),
IsLocked: pullRequest.IsLocked,
PullRequest: &pr,
IsPull: true,
}
if pullRequest.Closed != nil {
o.forgejoPullRequest.ClosedUnix = timeutil.TimeStamp(pullRequest.Closed.Unix())
}
}
func (o *pullRequest) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
project := f3_tree.GetProjectID(o.GetNode())
id := f3_util.ParseInt(string(node.GetID()))
issue, err := issues_model.GetIssueByIndex(ctx, project, id)
if issues_model.IsErrIssueNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("issue %v %w", id, err))
}
if err := issue.LoadAttributes(ctx); err != nil {
panic(err)
}
if err := issue.PullRequest.LoadHeadRepo(ctx); err != nil {
panic(err)
}
o.headRepository = o.repositoryToReference(ctx, issue.PullRequest.HeadRepo)
if err := issue.PullRequest.LoadBaseRepo(ctx); err != nil {
panic(err)
}
o.baseRepository = o.repositoryToReference(ctx, issue.PullRequest.BaseRepo)
o.forgejoPullRequest = issue
o.Trace("ID = %s", o.forgejoPullRequest.ID)
return true
}
func (o *pullRequest) Patch(ctx context.Context) {
node := o.GetNode()
project := f3_tree.GetProjectID(o.GetNode())
id := f3_util.ParseInt(string(node.GetID()))
o.Trace("repo_id = %d, index = %d", project, id)
if _, err := db.GetEngine(ctx).Where("`repo_id` = ? AND `index` = ?", project, id).Cols("name", "content").Update(o.forgejoPullRequest); err != nil {
panic(fmt.Errorf("%v %v", o.forgejoPullRequest, err))
}
}
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) generic.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
o.forgejoPullRequest.RepoID = f3_tree.GetProjectID(o.GetNode())
ctx, committer, err := db.TxContext(ctx)
if err != nil {
panic(err)
}
defer committer.Close()
idx, err := db.GetNextResourceIndex(ctx, "issue_index", o.forgejoPullRequest.RepoID)
if err != nil {
panic(fmt.Errorf("generate issue index failed: %w", err))
}
o.forgejoPullRequest.Index = idx
sess := db.GetEngine(ctx)
if _, err = sess.NoAutoTime().Insert(o.forgejoPullRequest); err != nil {
panic(err)
}
pr := o.forgejoPullRequest.PullRequest
pr.Index = o.forgejoPullRequest.Index
pr.IssueID = o.forgejoPullRequest.ID
pr.HeadRepoID = o.referenceToRepository(o.headRepository)
if pr.HeadRepoID == 0 {
panic(fmt.Errorf("HeadRepoID == 0 in %v", pr))
}
pr.BaseRepoID = o.referenceToRepository(o.baseRepository)
if pr.BaseRepoID == 0 {
panic(fmt.Errorf("BaseRepoID == 0 in %v", pr))
}
if _, err = sess.NoAutoTime().Insert(pr); err != nil {
panic(err)
}
if err = committer.Commit(); err != nil {
panic(fmt.Errorf("Commit: %w", err))
}
if err := pr.LoadBaseRepo(ctx); err != nil {
panic(err)
}
if err := pr.LoadHeadRepo(ctx); err != nil {
panic(err)
}
o.Trace("pullRequest created %d/%d", o.forgejoPullRequest.ID, o.forgejoPullRequest.Index)
return generic.NodeID(fmt.Sprintf("%d", o.forgejoPullRequest.Index))
}
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())
repoPath := repo_model.RepoPath(owner, project)
gitRepo, err := git.OpenRepository(ctx, repoPath)
if err != nil {
panic(err)
}
defer gitRepo.Close()
doer, err := user_model.GetAdminUser(ctx)
if err != nil {
panic(fmt.Errorf("GetAdminUser %w", err))
}
if err := issue_service.DeleteIssue(ctx, doer, gitRepo, o.forgejoPullRequest); err != nil {
panic(err)
}
}
func newPullRequest() generic.NodeDriverInterface {
return &pullRequest{}
}

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 driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/optional"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type pullRequests struct {
container
}
func (o *pullRequests) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
project := f3_tree.GetProjectID(o.GetNode())
forgejoPullRequests, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
Paginator: &db.ListOptions{Page: page, PageSize: pageSize},
RepoIDs: []int64{project},
IsPull: optional.Some(true),
})
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{}
}

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 driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &reaction{}
type reaction struct {
common
forgejoReaction *issues_model.Reaction
}
func (o *reaction) SetNative(reaction any) {
o.forgejoReaction = reaction.(*issues_model.Reaction)
}
func (o *reaction) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoReaction.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(fmt.Sprintf("%d", o.forgejoReaction.ID)),
UserID: f3_tree.NewUserReference(o.forgejoReaction.User.ID),
Content: o.forgejoReaction.Type,
}
}
func (o *reaction) FromFormat(content f3.Interface) {
reaction := content.(*f3.Reaction)
o.forgejoReaction = &issues_model.Reaction{
ID: f3_util.ParseInt(reaction.GetID()),
UserID: reaction.UserID.GetIDAsInt(),
User: &user_model.User{
ID: reaction.UserID.GetIDAsInt(),
},
Type: reaction.Content,
}
}
func (o *reaction) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
id := f3_util.ParseInt(string(node.GetID()))
if has, err := db.GetEngine(ctx).Where("ID = ?", id).Get(o.forgejoReaction); err != nil {
panic(fmt.Errorf("reaction %v %w", id, err))
} else if !has {
return false
}
if _, err := o.forgejoReaction.LoadUser(ctx); err != nil {
panic(fmt.Errorf("LoadUser %v %w", *o.forgejoReaction, err))
}
return true
}
func (o *reaction) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoReaction.ID)
if _, err := db.GetEngine(ctx).ID(o.forgejoReaction.ID).Cols("type").Update(o.forgejoReaction); err != nil {
panic(fmt.Errorf("UpdateReactionCols: %v %v", o.forgejoReaction, err))
}
}
func (o *reaction) Put(ctx context.Context) generic.NodeID {
o.Error("%v", o.forgejoReaction.User)
sess := db.GetEngine(ctx)
reactionable := f3_tree.GetReactionable(o.GetNode())
reactionableID := f3_tree.GetReactionableID(o.GetNode())
switch reactionable.GetKind() {
case f3_tree.KindIssue, f3_tree.KindPullRequest:
project := f3_tree.GetProjectID(o.GetNode())
issue, err := issues_model.GetIssueByIndex(ctx, project, reactionableID)
if err != nil {
panic(fmt.Errorf("GetIssueByIndex %v %w", reactionableID, err))
}
o.forgejoReaction.IssueID = issue.ID
case f3_tree.KindComment:
o.forgejoReaction.CommentID = reactionableID
default:
panic(fmt.Errorf("unexpected type %v", reactionable.GetKind()))
}
o.Error("%v", o.forgejoReaction)
if _, err := sess.Insert(o.forgejoReaction); err != nil {
panic(err)
}
o.Trace("reaction created %d", o.forgejoReaction.ID)
return generic.NodeID(fmt.Sprintf("%d", o.forgejoReaction.ID))
}
func (o *reaction) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
sess := db.GetEngine(ctx)
if _, err := sess.Delete(o.forgejoReaction); err != nil {
panic(err)
}
}
func newReaction() generic.NodeDriverInterface {
return &reaction{}
}

View file

@ -0,0 +1,59 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"xorm.io/builder"
)
type reactions struct {
container
}
func (o *reactions) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
reactionable := f3_tree.GetReactionable(o.GetNode())
reactionableID := f3_tree.GetReactionableID(o.GetNode())
sess := db.GetEngine(ctx)
cond := builder.NewCond()
switch reactionable.GetKind() {
case f3_tree.KindIssue, f3_tree.KindPullRequest:
project := f3_tree.GetProjectID(o.GetNode())
issue, err := issues_model.GetIssueByIndex(ctx, project, reactionableID)
if err != nil {
panic(fmt.Errorf("GetIssueByIndex %v %w", reactionableID, err))
}
cond = cond.And(builder.Eq{"reaction.issue_id": issue.ID})
case f3_tree.KindComment:
cond = cond.And(builder.Eq{"reaction.comment_id": reactionableID})
default:
panic(fmt.Errorf("unexpected type %v", reactionable.GetKind()))
}
sess = sess.Where(cond)
if page > 0 {
sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: pageSize})
}
reactions := make([]*issues_model.Reaction, 0, 10)
if err := sess.Find(&reactions); err != nil {
panic(fmt.Errorf("error while listing reactions: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(reactions...)...)
}
func newReactions() generic.NodeDriverInterface {
return &reactions{}
}

View file

@ -0,0 +1,161 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/timeutil"
release_service "code.gitea.io/gitea/services/release"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &release{}
type release struct {
common
forgejoRelease *repo_model.Release
}
func (o *release) SetNative(release any) {
o.forgejoRelease = release.(*repo_model.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(fmt.Sprintf("%d", o.forgejoRelease.ID)),
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.CreatedUnix.AsTime(),
}
}
func (o *release) FromFormat(content f3.Interface) {
release := content.(*f3.Release)
o.forgejoRelease = &repo_model.Release{
ID: f3_util.ParseInt(release.GetID()),
PublisherID: release.PublisherID.GetIDAsInt(),
Publisher: &user_model.User{
ID: release.PublisherID.GetIDAsInt(),
},
TagName: release.TagName,
LowerTagName: strings.ToLower(release.TagName),
Target: release.TargetCommitish,
Title: release.Name,
Note: release.Body,
IsDraft: release.Draft,
IsPrerelease: release.Prerelease,
IsTag: false,
CreatedUnix: timeutil.TimeStamp(release.Created.Unix()),
}
}
func (o *release) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
id := f3_util.ParseInt(string(node.GetID()))
release, err := repo_model.GetReleaseByID(ctx, id)
if repo_model.IsErrReleaseNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("release %v %w", id, err))
}
release.Publisher, err = user_model.GetUserByID(ctx, release.PublisherID)
if err != nil {
if user_model.IsErrUserNotExist(err) {
release.Publisher = user_model.NewGhostUser()
} else {
panic(err)
}
}
o.forgejoRelease = release
return true
}
func (o *release) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoRelease.ID)
if _, err := db.GetEngine(ctx).ID(o.forgejoRelease.ID).Cols("title", "note").Update(o.forgejoRelease); err != nil {
panic(fmt.Errorf("UpdateReleaseCols: %v %v", o.forgejoRelease, err))
}
}
func (o *release) Put(ctx context.Context) generic.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
o.forgejoRelease.RepoID = f3_tree.GetProjectID(o.GetNode())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
repoPath := repo_model.RepoPath(owner, project)
gitRepo, err := git.OpenRepository(ctx, repoPath)
if err != nil {
panic(err)
}
defer gitRepo.Close()
if err := release_service.CreateRelease(gitRepo, o.forgejoRelease, nil, ""); err != nil {
panic(err)
}
o.Trace("release created %d", o.forgejoRelease.ID)
return generic.NodeID(fmt.Sprintf("%d", o.forgejoRelease.ID))
}
func (o *release) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
project := f3_tree.GetProjectID(o.GetNode())
repo, err := repo_model.GetRepositoryByID(ctx, project)
if err != nil {
panic(err)
}
doer, err := user_model.GetAdminUser(ctx)
if err != nil {
panic(fmt.Errorf("GetAdminUser %w", err))
}
if err := release_service.DeleteReleaseByID(ctx, repo, o.forgejoRelease, doer, true); err != nil {
panic(err)
}
}
func newRelease() generic.NodeDriverInterface {
return &release{}
}

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 driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type releases struct {
container
}
func (o *releases) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
project := f3_tree.GetProjectID(o.GetNode())
forgejoReleases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
ListOptions: db.ListOptions{Page: page, PageSize: pageSize},
IncludeDrafts: true,
IncludeTags: false,
RepoID: project,
})
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,36 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"code.forgejo.org/f3/gof3/v3/f3"
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 newRepositories() generic.NodeDriverInterface {
return &repositories{}
}

View file

@ -0,0 +1,101 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
repo_model "code.gitea.io/gitea/models/repo"
"code.forgejo.org/f3/gof3/v3/f3"
helpers_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/repository"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
var _ f3_tree.ForgeDriverInterface = &repository{}
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) generic.NodeID {
return o.upsert(ctx)
}
func (o *repository) Patch(ctx context.Context) {
o.upsert(ctx)
}
func (o *repository) upsert(ctx context.Context) generic.NodeID {
o.Trace("%s", o.GetNativeID())
o.h.Upsert(ctx, o.f)
return generic.NodeID(o.f.Name)
}
func (o *repository) SetFetchFunc(fetchFunc func(ctx context.Context, destination string)) {
o.f.FetchFunc = fetchFunc
}
func (o *repository) getURL() string {
owner := f3_tree.GetOwnerName(o.GetNode())
repoName := f3_tree.GetProjectName(o.GetNode())
if o.f.GetID() == f3.RepositoryNameWiki {
repoName += ".wiki"
}
return repo_model.RepoPath(owner, repoName)
}
func (o *repository) GetRepositoryURL() string {
return o.getURL()
}
func (o *repository) GetRepositoryPushURL() string {
return o.getURL()
}
func newRepository(ctx context.Context) generic.NodeDriverInterface {
r := &repository{
f: &f3.Repository{},
}
r.h = helpers_repository.NewHelper(r)
return r
}

View file

@ -0,0 +1,179 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &review{}
type review struct {
common
forgejoReview *issues_model.Review
}
func (o *review) SetNative(review any) {
o.forgejoReview = review.(*issues_model.Review)
}
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())
}
func (o *review) ToFormat() f3.Interface {
if o.forgejoReview == nil {
return o.NewFormat()
}
review := &f3.Review{
Common: f3.NewCommon(o.GetNativeID()),
ReviewerID: f3_tree.NewUserReference(o.forgejoReview.ReviewerID),
Official: o.forgejoReview.Official,
CommitID: o.forgejoReview.CommitID,
Content: o.forgejoReview.Content,
CreatedAt: o.forgejoReview.CreatedUnix.AsTime(),
}
switch o.forgejoReview.Type {
case issues_model.ReviewTypeApprove:
review.State = f3.ReviewStateApproved
case issues_model.ReviewTypeReject:
review.State = f3.ReviewStateChangesRequested
case issues_model.ReviewTypeComment:
review.State = f3.ReviewStateCommented
case issues_model.ReviewTypePending:
review.State = f3.ReviewStatePending
case issues_model.ReviewTypeRequest:
review.State = f3.ReviewStateRequestReview
default:
review.State = f3.ReviewStateUnknown
}
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 = &issues_model.Review{
ID: f3_util.ParseInt(review.GetID()),
ReviewerID: review.ReviewerID.GetIDAsInt(),
Reviewer: &user_model.User{
ID: review.ReviewerID.GetIDAsInt(),
},
Official: review.Official,
CommitID: review.CommitID,
Content: review.Content,
CreatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
}
switch review.State {
case f3.ReviewStateApproved:
o.forgejoReview.Type = issues_model.ReviewTypeApprove
case f3.ReviewStateChangesRequested:
o.forgejoReview.Type = issues_model.ReviewTypeReject
case f3.ReviewStateCommented:
o.forgejoReview.Type = issues_model.ReviewTypeComment
case f3.ReviewStatePending:
o.forgejoReview.Type = issues_model.ReviewTypePending
case f3.ReviewStateRequestReview:
o.forgejoReview.Type = issues_model.ReviewTypeRequest
default:
o.forgejoReview.Type = issues_model.ReviewTypeUnknown
}
}
func (o *review) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
id := f3_util.ParseInt(string(node.GetID()))
review, err := issues_model.GetReviewByID(ctx, id)
if issues_model.IsErrReviewNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("review %v %w", id, err))
}
if err := review.LoadReviewer(ctx); err != nil {
panic(fmt.Errorf("LoadReviewer %v %w", *review, err))
}
o.forgejoReview = review
return true
}
func (o *review) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoReview.ID)
if _, err := db.GetEngine(ctx).ID(o.forgejoReview.ID).Cols("content").Update(o.forgejoReview); err != nil {
panic(fmt.Errorf("UpdateReviewCols: %v %v", o.forgejoReview, err))
}
}
func (o *review) Put(ctx context.Context) generic.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
project := f3_tree.GetProjectID(o.GetNode())
pullRequest := f3_tree.GetPullRequestID(o.GetNode())
issue, err := issues_model.GetIssueByIndex(ctx, project, pullRequest)
if err != nil {
panic(fmt.Errorf("GetIssueByIndex %v", err))
}
o.forgejoReview.IssueID = issue.ID
sess := db.GetEngine(ctx)
if _, err := sess.NoAutoTime().Insert(o.forgejoReview); err != nil {
panic(err)
}
o.Trace("review created %d", o.forgejoReview.ID)
return generic.NodeID(fmt.Sprintf("%d", o.forgejoReview.ID))
}
func (o *review) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
project := f3_tree.GetProjectID(o.GetNode())
pullRequest := f3_tree.GetPullRequestID(o.GetNode())
issue, err := issues_model.GetIssueByIndex(ctx, project, pullRequest)
if err != nil {
panic(fmt.Errorf("GetIssueByIndex %v", err))
}
o.forgejoReview.IssueID = issue.ID
if err := issues_model.DeleteReview(ctx, o.forgejoReview); err != nil {
panic(err)
}
}
func newReview() generic.NodeDriverInterface {
return &review{}
}

View file

@ -0,0 +1,142 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &reviewComment{}
type reviewComment struct {
common
forgejoReviewComment *issues_model.Comment
}
func (o *reviewComment) SetNative(reviewComment any) {
o.forgejoReviewComment = reviewComment.(*issues_model.Comment)
}
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 patch2diff(patch string) string {
split := strings.Split(patch, "\n@@")
if len(split) == 2 {
return "@@" + split[1]
}
return patch
}
func (o *reviewComment) ToFormat() f3.Interface {
if o.forgejoReviewComment == nil {
return o.NewFormat()
}
return &f3.ReviewComment{
Common: f3.NewCommon(o.GetNativeID()),
PosterID: f3_tree.NewUserReference(o.forgejoReviewComment.Poster.ID),
Content: o.forgejoReviewComment.Content,
TreePath: o.forgejoReviewComment.TreePath,
DiffHunk: patch2diff(o.forgejoReviewComment.PatchQuoted),
Line: int(o.forgejoReviewComment.Line),
CommitID: o.forgejoReviewComment.CommitSHA,
CreatedAt: o.forgejoReviewComment.CreatedUnix.AsTime(),
UpdatedAt: o.forgejoReviewComment.UpdatedUnix.AsTime(),
}
}
func (o *reviewComment) FromFormat(content f3.Interface) {
reviewComment := content.(*f3.ReviewComment)
o.forgejoReviewComment = &issues_model.Comment{
ID: f3_util.ParseInt(reviewComment.GetID()),
PosterID: reviewComment.PosterID.GetIDAsInt(),
Poster: &user_model.User{
ID: reviewComment.PosterID.GetIDAsInt(),
},
TreePath: reviewComment.TreePath,
Content: reviewComment.Content,
// a hunk misses the patch header but it is never used so do not bother
// reconstructing it
Patch: reviewComment.DiffHunk,
PatchQuoted: reviewComment.DiffHunk,
Line: int64(reviewComment.Line),
CommitSHA: reviewComment.CommitID,
CreatedUnix: timeutil.TimeStamp(reviewComment.CreatedAt.Unix()),
UpdatedUnix: timeutil.TimeStamp(reviewComment.UpdatedAt.Unix()),
}
}
func (o *reviewComment) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
id := f3_util.ParseInt(string(node.GetID()))
reviewComment, err := issues_model.GetCommentByID(ctx, id)
if issues_model.IsErrCommentNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("reviewComment %v %w", id, err))
}
if err := reviewComment.LoadPoster(ctx); err != nil {
panic(fmt.Errorf("LoadPoster %v %w", *reviewComment, err))
}
o.forgejoReviewComment = reviewComment
return true
}
func (o *reviewComment) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoReviewComment.ID)
if _, err := db.GetEngine(ctx).ID(o.forgejoReviewComment.ID).Cols("content").Update(o.forgejoReviewComment); err != nil {
panic(fmt.Errorf("UpdateReviewCommentCols: %v %v", o.forgejoReviewComment, err))
}
}
func (o *reviewComment) Put(ctx context.Context) generic.NodeID {
node := o.GetNode()
o.Trace("%s", node.GetID())
sess := db.GetEngine(ctx)
if _, err := sess.NoAutoTime().Insert(o.forgejoReviewComment); err != nil {
panic(err)
}
o.Trace("reviewComment created %d", o.forgejoReviewComment.ID)
return generic.NodeID(fmt.Sprintf("%d", o.forgejoReviewComment.ID))
}
func (o *reviewComment) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
if err := issues_model.DeleteComment(ctx, o.forgejoReviewComment); err != nil {
panic(err)
}
}
func newReviewComment() generic.NodeDriverInterface {
return &reviewComment{}
}

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 driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
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 {
pageSize := o.getPageSize()
id := f3_tree.GetReviewID(o.GetNode())
sess := db.GetEngine(ctx).
Table("comment").
Where("`review_id` = ? AND `type` = ?", id, issues_model.CommentTypeCode)
if page != 0 {
sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: pageSize})
}
forgejoReviewComments := make([]*issues_model.Comment, 0, pageSize)
if err := sess.Find(&forgejoReviewComments); err != nil {
panic(fmt.Errorf("error while listing reviewComments: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoReviewComments...)...)
}
func newReviewComments() generic.NodeDriverInterface {
return &reviewComments{}
}

View file

@ -0,0 +1,49 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type reviews struct {
container
}
func (o *reviews) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
project := f3_tree.GetProjectID(o.GetNode())
pullRequest := f3_tree.GetPullRequestID(o.GetNode())
issue, err := issues_model.GetIssueByIndex(ctx, project, pullRequest)
if err != nil {
panic(fmt.Errorf("GetIssueByIndex %v %w", pullRequest, err))
}
sess := db.GetEngine(ctx).
Table("review").
Where("`issue_id` = ?", issue.ID)
if page != 0 {
sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: pageSize})
}
forgejoReviews := make([]*issues_model.Review, 0, pageSize)
if err := sess.Find(&forgejoReviews); err != nil {
panic(fmt.Errorf("error while listing reviews: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoReviews...)...)
}
func newReviews() generic.NodeDriverInterface {
return &reviews{}
}

View file

@ -0,0 +1,41 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"code.forgejo.org/f3/gof3/v3/f3"
"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) generic.NodeID {
return generic.NilID
}
func (o *root) Patch(context.Context) {
}

View file

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

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 tests
import (
"testing"
driver_options "code.gitea.io/gitea/services/f3/driver/options"
"code.forgejo.org/f3/gof3/v3/options"
"code.forgejo.org/f3/gof3/v3/tree/generic"
forge_test "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
)
type forgeTest struct {
forge_test.Base
}
func (o *forgeTest) NewOptions(t *testing.T) options.Interface {
return newTestOptions(t)
}
func (o *forgeTest) GetExceptions() []generic.Kind {
return []generic.Kind{}
}
func (o *forgeTest) GetNonTestUsers() []string {
return []string{
"user1",
}
}
func newForgeTest() forge_test.Interface {
t := &forgeTest{}
t.SetName(driver_options.Name)
return t
}

View file

@ -0,0 +1,21 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package tests
import (
"testing"
forgejo_log "code.gitea.io/gitea/modules/log"
driver_options "code.gitea.io/gitea/services/f3/driver/options"
"code.gitea.io/gitea/services/f3/util"
"code.forgejo.org/f3/gof3/v3/options"
)
func newTestOptions(t *testing.T) options.Interface {
o := options.GetFactory(driver_options.Name)().(*driver_options.Options)
o.SetLogger(util.NewF3Logger(nil, forgejo_log.GetLogger(forgejo_log.DEFAULT)))
return o
}

111
services/f3/driver/topic.go Normal file
View file

@ -0,0 +1,111 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &topic{}
type topic struct {
common
forgejoTopic *repo_model.Topic
}
func (o *topic) SetNative(topic any) {
o.forgejoTopic = topic.(*repo_model.Topic)
}
func (o *topic) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoTopic.ID)
}
func (o *topic) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *topic) ToFormat() f3.Interface {
if o.forgejoTopic == nil {
return o.NewFormat()
}
return &f3.Topic{
Common: f3.NewCommon(o.GetNativeID()),
Name: o.forgejoTopic.Name,
}
}
func (o *topic) FromFormat(content f3.Interface) {
topic := content.(*f3.Topic)
o.forgejoTopic = &repo_model.Topic{
ID: f3_util.ParseInt(topic.GetID()),
Name: topic.Name,
}
}
func (o *topic) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
id := f3_util.ParseInt(string(node.GetID()))
if has, err := db.GetEngine(ctx).Where("ID = ?", id).Get(o.forgejoTopic); err != nil {
panic(fmt.Errorf("topic %v %w", id, err))
} else if !has {
return false
}
return true
}
func (o *topic) Patch(ctx context.Context) {
o.Trace("%d", o.forgejoTopic.ID)
if _, err := db.GetEngine(ctx).ID(o.forgejoTopic.ID).Cols("name").Update(o.forgejoTopic); err != nil {
panic(fmt.Errorf("UpdateTopicCols: %v %v", o.forgejoTopic, err))
}
}
func (o *topic) Put(ctx context.Context) generic.NodeID {
sess := db.GetEngine(ctx)
if _, err := sess.Insert(o.forgejoTopic); err != nil {
panic(err)
}
o.Trace("topic created %d", o.forgejoTopic.ID)
return generic.NodeID(fmt.Sprintf("%d", o.forgejoTopic.ID))
}
func (o *topic) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
sess := db.GetEngine(ctx)
if _, err := sess.Delete(&repo_model.RepoTopic{
TopicID: o.forgejoTopic.ID,
}); err != nil {
panic(fmt.Errorf("Delete RepoTopic for %v %v", o.forgejoTopic, err))
}
if _, err := sess.Delete(o.forgejoTopic); err != nil {
panic(fmt.Errorf("Delete Topic %v %v", o.forgejoTopic, err))
}
}
func newTopic() generic.NodeDriverInterface {
return &topic{}
}

View file

@ -0,0 +1,41 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type topics struct {
container
}
func (o *topics) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
pageSize := o.getPageSize()
sess := db.GetEngine(ctx)
if page != 0 {
sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: pageSize})
}
sess = sess.Select("`topic`.*")
topics := make([]*repo_model.Topic, 0, pageSize)
if err := sess.Find(&topics); err != nil {
panic(fmt.Errorf("error while listing topics: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(topics...)...)
}
func newTopics() generic.NodeDriverInterface {
return &topics{}
}

104
services/f3/driver/tree.go Normal file
View file

@ -0,0 +1,104 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
forgejo_options "code.gitea.io/gitea/services/f3/driver/options"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type treeDriver struct {
generic.NullTreeDriver
options *forgejo_options.Options
}
func (o *treeDriver) Init() {
o.NullTreeDriver.Init()
}
func (o *treeDriver) Factory(ctx context.Context, kind generic.Kind) generic.NodeDriverInterface {
switch kind {
case f3_tree.KindForge:
return newForge()
case f3_tree.KindOrganizations:
return newOrganizations()
case f3_tree.KindOrganization:
return newOrganization()
case f3_tree.KindUsers:
return newUsers()
case f3_tree.KindUser:
return newUser()
case f3_tree.KindProjects:
return newProjects()
case f3_tree.KindProject:
return newProject()
case f3_tree.KindIssues:
return newIssues()
case f3_tree.KindIssue:
return newIssue()
case f3_tree.KindComments:
return newComments()
case f3_tree.KindComment:
return newComment()
case f3_tree.KindAssets:
return newAssets()
case f3_tree.KindAsset:
return newAsset()
case f3_tree.KindLabels:
return newLabels()
case f3_tree.KindLabel:
return newLabel()
case f3_tree.KindReactions:
return newReactions()
case f3_tree.KindReaction:
return newReaction()
case f3_tree.KindReviews:
return newReviews()
case f3_tree.KindReview:
return newReview()
case f3_tree.KindReviewComments:
return newReviewComments()
case f3_tree.KindReviewComment:
return newReviewComment()
case f3_tree.KindMilestones:
return newMilestones()
case f3_tree.KindMilestone:
return newMilestone()
case f3_tree.KindPullRequests:
return newPullRequests()
case f3_tree.KindPullRequest:
return newPullRequest()
case f3_tree.KindReleases:
return newReleases()
case f3_tree.KindRelease:
return newRelease()
case f3_tree.KindTopics:
return newTopics()
case f3_tree.KindTopic:
return newTopic()
case f3_tree.KindRepositories:
return newRepositories()
case f3_tree.KindRepository:
return newRepository(ctx)
case generic.KindRoot:
return newRoot(o.GetTree().(f3_tree.TreeInterface).NewFormat(kind))
default:
panic(fmt.Errorf("unexpected kind %s", kind))
}
}
func newTreeDriver(tree generic.TreeInterface, anyOptions any) generic.TreeDriverInterface {
driver := &treeDriver{
options: anyOptions.(*forgejo_options.Options),
}
driver.Init()
return driver
}

128
services/f3/driver/user.go Normal file
View file

@ -0,0 +1,128 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package driver
import (
"context"
"fmt"
"strings"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional"
user_service "code.gitea.io/gitea/services/user"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
f3_util "code.forgejo.org/f3/gof3/v3/util"
)
var _ f3_tree.ForgeDriverInterface = &user{}
type user struct {
common
forgejoUser *user_model.User
}
func getSystemUserByName(name string) *user_model.User {
switch name {
case user_model.GhostUserName:
return user_model.NewGhostUser()
case user_model.ActionsUserName:
return user_model.NewActionsUser()
default:
return nil
}
}
func (o *user) SetNative(user any) {
o.forgejoUser = user.(*user_model.User)
}
func (o *user) GetNativeID() string {
return fmt.Sprintf("%d", o.forgejoUser.ID)
}
func (o *user) NewFormat() f3.Interface {
node := o.GetNode()
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *user) ToFormat() f3.Interface {
if o.forgejoUser == nil {
return o.NewFormat()
}
return &f3.User{
Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoUser.ID)),
UserName: o.forgejoUser.Name,
Name: o.forgejoUser.FullName,
Email: o.forgejoUser.Email,
IsAdmin: o.forgejoUser.IsAdmin,
Password: o.forgejoUser.Passwd,
}
}
func (o *user) FromFormat(content f3.Interface) {
user := content.(*f3.User)
o.forgejoUser = &user_model.User{
Type: user_model.UserTypeRemoteUser,
ID: f3_util.ParseInt(user.GetID()),
Name: user.UserName,
FullName: user.Name,
Email: user.Email,
IsAdmin: user.IsAdmin,
Passwd: user.Password,
}
}
func (o *user) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
id := f3_util.ParseInt(string(node.GetID()))
u, err := user_model.GetPossibleUserByID(ctx, id)
if user_model.IsErrUserNotExist(err) {
return false
}
if err != nil {
panic(fmt.Errorf("user %v %w", id, err))
}
o.forgejoUser = u
return true
}
func (o *user) Patch(context.Context) {
}
func (o *user) Put(ctx context.Context) generic.NodeID {
if user := getSystemUserByName(o.forgejoUser.Name); user != nil {
return generic.NodeID(fmt.Sprintf("%d", user.ID))
}
o.forgejoUser.LowerName = strings.ToLower(o.forgejoUser.Name)
o.Trace("%v", *o.forgejoUser)
overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: optional.Some(true),
}
err := user_model.CreateUser(ctx, o.forgejoUser, overwriteDefault)
if err != nil {
panic(err)
}
return generic.NodeID(fmt.Sprintf("%d", o.forgejoUser.ID))
}
func (o *user) Delete(ctx context.Context) {
node := o.GetNode()
o.Trace("%s", node.GetID())
if err := user_service.DeleteUser(ctx, o.forgejoUser, true); err != nil {
panic(err)
}
}
func newUser() generic.NodeDriverInterface {
return &user{}
}

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 driver
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
type users struct {
container
}
func (o *users) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
sess := db.GetEngine(ctx).In("type", user_model.UserTypeIndividual, user_model.UserTypeRemoteUser)
if page != 0 {
sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: o.getPageSize()})
}
sess = sess.Select("`user`.*")
users := make([]*user_model.User, 0, o.getPageSize())
if err := sess.Find(&users); err != nil {
panic(fmt.Errorf("error while listing users: %v", err))
}
return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(users...)...)
}
func (o *users) GetIDFromName(ctx context.Context, name string) generic.NodeID {
user, err := user_model.GetUserByName(ctx, name)
if err != nil {
panic(fmt.Errorf("GetUserByName: %v", err))
}
return generic.NodeID(fmt.Sprintf("%d", user.ID))
}
func newUsers() generic.NodeDriverInterface {
return &users{}
}

View file

@ -0,0 +1,97 @@
// Copyright Earl Warren <contact@earl-warren.org>
// SPDX-License-Identifier: MIT
package util
import (
"fmt"
forgejo_log "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migration"
"code.forgejo.org/f3/gof3/v3/logger"
)
type f3Logger struct {
m migration.Messenger
l forgejo_log.Logger
}
func (o *f3Logger) Message(message string, args ...any) {
if o.m != nil {
o.m(message, args...)
}
}
func (o *f3Logger) SetLevel(level logger.Level) {
}
func forgejoLevelToF3Level(level forgejo_log.Level) logger.Level {
switch level {
case forgejo_log.TRACE:
return logger.Trace
case forgejo_log.DEBUG:
return logger.Debug
case forgejo_log.INFO:
return logger.Info
case forgejo_log.WARN:
return logger.Warn
case forgejo_log.ERROR:
return logger.Error
case forgejo_log.FATAL:
return logger.Fatal
default:
panic(fmt.Errorf("unexpected level %d", level))
}
}
func f3LevelToForgejoLevel(level logger.Level) forgejo_log.Level {
switch level {
case logger.Trace:
return forgejo_log.TRACE
case logger.Debug:
return forgejo_log.DEBUG
case logger.Info:
return forgejo_log.INFO
case logger.Warn:
return forgejo_log.WARN
case logger.Error:
return forgejo_log.ERROR
case logger.Fatal:
return forgejo_log.FATAL
default:
panic(fmt.Errorf("unexpected level %d", level))
}
}
func (o *f3Logger) GetLevel() logger.Level {
return forgejoLevelToF3Level(o.l.GetLevel())
}
func (o *f3Logger) Log(skip int, level logger.Level, format string, args ...any) {
o.l.Log(skip+1, f3LevelToForgejoLevel(level), format, args...)
}
func (o *f3Logger) Trace(message string, args ...any) {
o.l.Log(1, forgejo_log.TRACE, message, args...)
}
func (o *f3Logger) Debug(message string, args ...any) {
o.l.Log(1, forgejo_log.DEBUG, message, args...)
}
func (o *f3Logger) Info(message string, args ...any) { o.l.Log(1, forgejo_log.INFO, message, args...) }
func (o *f3Logger) Warn(message string, args ...any) { o.l.Log(1, forgejo_log.WARN, message, args...) }
func (o *f3Logger) Error(message string, args ...any) {
o.l.Log(1, forgejo_log.ERROR, message, args...)
}
func (o *f3Logger) Fatal(message string, args ...any) {
o.l.Log(1, forgejo_log.FATAL, message, args...)
}
func NewF3Logger(messenger migration.Messenger, logger forgejo_log.Logger) logger.Interface {
return &f3Logger{
m: messenger,
l: logger,
}
}

View file

@ -0,0 +1,89 @@
// Copyright Earl Warren <contact@earl-warren.org>
// SPDX-License-Identifier: MIT
package util
import (
"fmt"
"testing"
"time"
forgejo_log "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/test"
"code.forgejo.org/f3/gof3/v3/logger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestF3UtilMessage(t *testing.T) {
expected := "EXPECTED MESSAGE"
var actual string
logger := NewF3Logger(func(message string, args ...any) {
actual = fmt.Sprintf(message, args...)
}, nil)
logger.Message("EXPECTED %s", "MESSAGE")
assert.EqualValues(t, expected, actual)
}
func TestF3UtilLogger(t *testing.T) {
for _, testCase := range []struct {
level logger.Level
call func(logger.MessageInterface, string, ...any)
}{
{level: logger.Trace, call: func(logger logger.MessageInterface, message string, args ...any) { logger.Trace(message, args...) }},
{level: logger.Debug, call: func(logger logger.MessageInterface, message string, args ...any) { logger.Debug(message, args...) }},
{level: logger.Info, call: func(logger logger.MessageInterface, message string, args ...any) { logger.Info(message, args...) }},
{level: logger.Warn, call: func(logger logger.MessageInterface, message string, args ...any) { logger.Warn(message, args...) }},
{level: logger.Error, call: func(logger logger.MessageInterface, message string, args ...any) { logger.Error(message, args...) }},
{level: logger.Fatal, call: func(logger logger.MessageInterface, message string, args ...any) { logger.Fatal(message, args...) }},
} {
t.Run(testCase.level.String(), func(t *testing.T) {
testLoggerCase(t, testCase.level, testCase.call)
})
}
}
func testLoggerCase(t *testing.T, level logger.Level, loggerFunc func(logger.MessageInterface, string, ...any)) {
lc, cleanup := test.NewLogChecker(forgejo_log.DEFAULT, f3LevelToForgejoLevel(level))
defer cleanup()
stopMark := "STOP"
lc.StopMark(stopMark)
filtered := []string{
"MESSAGE HERE",
}
moreVerbose := logger.MoreVerbose(level)
if moreVerbose != nil {
filtered = append(filtered, "MESSAGE MORE VERBOSE")
}
lessVerbose := logger.LessVerbose(level)
if lessVerbose != nil {
filtered = append(filtered, "MESSAGE LESS VERBOSE")
}
lc.Filter(filtered...)
logger := NewF3Logger(nil, forgejo_log.GetLogger(forgejo_log.DEFAULT))
loggerFunc(logger, "MESSAGE %s", "HERE")
if moreVerbose != nil {
logger.Log(1, *moreVerbose, "MESSAGE %s", "MORE VERBOSE")
}
if lessVerbose != nil {
logger.Log(1, *lessVerbose, "MESSAGE %s", "LESS VERBOSE")
}
logger.Fatal(stopMark)
logFiltered, logStopped := lc.Check(5 * time.Second)
assert.True(t, logStopped)
i := 0
assert.True(t, logFiltered[i], filtered[i])
if moreVerbose != nil {
i++
require.True(t, len(logFiltered) > i)
assert.False(t, logFiltered[i], filtered[i])
}
if lessVerbose != nil {
i++
require.True(t, len(logFiltered) > i)
assert.True(t, logFiltered[i], filtered[i])
}
}

View file

@ -0,0 +1,135 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package integration
import (
"context"
"fmt"
"testing"
"code.gitea.io/gitea/cmd/forgejo"
"code.gitea.io/gitea/services/f3/driver/options"
"code.gitea.io/gitea/tests"
_ "code.gitea.io/gitea/services/f3/driver"
_ "code.gitea.io/gitea/services/f3/driver/tests"
f3_filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
f3_logger "code.forgejo.org/f3/gof3/v3/logger"
f3_options "code.forgejo.org/f3/gof3/v3/options"
f3_generic "code.forgejo.org/f3/gof3/v3/tree/generic"
f3_tests "code.forgejo.org/f3/gof3/v3/tree/tests/f3"
f3_tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)
func runApp(ctx context.Context, args ...string) (string, error) {
l := f3_logger.NewCaptureLogger()
ctx = f3_logger.ContextSetLogger(ctx, l)
ctx = forgejo.ContextSetNoInit(ctx, true)
app := cli.NewApp()
app.Writer = l.GetBuffer()
app.ErrWriter = l.GetBuffer()
defer func() {
if r := recover(); r != nil {
fmt.Println(l.String())
panic(r)
}
}()
app.Commands = []*cli.Command{
forgejo.SubcmdF3Mirror(ctx),
}
err := app.Run(args)
fmt.Println(l.String())
return l.String(), err
}
func TestF3_CmdMirror_LocalForgejo(t *testing.T) {
defer tests.PrepareTestEnv(t)()
ctx := context.Background()
mirrorOptions := f3_tests_forge.GetFactory(options.Name)().NewOptions(t)
mirrorTree := f3_generic.GetFactory("f3")(ctx, mirrorOptions)
fixtureOptions := f3_tests_forge.GetFactory(f3_filesystem_options.Name)().NewOptions(t)
fixtureTree := f3_generic.GetFactory("f3")(ctx, fixtureOptions)
log := fixtureTree.GetLogger()
creator := f3_tests.NewCreator(t, log)
log.Trace("======= build fixture")
var fromPath string
{
fixtureUserID := "userID01"
fixtureProjectID := "projectID01"
userFormat := creator.GenerateUser()
userFormat.SetID(fixtureUserID)
users := fixtureTree.MustFind(f3_generic.NewPathFromString("/forge/users"))
user := users.CreateChild(ctx)
user.FromFormat(userFormat)
user.Upsert(ctx)
require.EqualValues(t, user.GetID(), users.GetIDFromName(ctx, userFormat.UserName))
projectFormat := creator.GenerateProject()
projectFormat.SetID(fixtureProjectID)
projects := user.MustFind(f3_generic.NewPathFromString("projects"))
project := projects.CreateChild(ctx)
project.FromFormat(projectFormat)
project.Upsert(ctx)
require.EqualValues(t, project.GetID(), projects.GetIDFromName(ctx, projectFormat.Name))
fromPath = fmt.Sprintf("/forge/users/%s/projects/%s", userFormat.UserName, projectFormat.Name)
}
log.Trace("======= create mirror")
var toPath string
var projects f3_generic.NodeInterface
{
userFormat := creator.GenerateUser()
users := mirrorTree.MustFind(f3_generic.NewPathFromString("/forge/users"))
user := users.CreateChild(ctx)
user.FromFormat(userFormat)
user.Upsert(ctx)
require.EqualValues(t, user.GetID(), users.GetIDFromName(ctx, userFormat.UserName))
projectFormat := creator.GenerateProject()
projects = user.MustFind(f3_generic.NewPathFromString("projects"))
project := projects.CreateChild(ctx)
project.FromFormat(projectFormat)
project.Upsert(ctx)
require.EqualValues(t, project.GetID(), projects.GetIDFromName(ctx, projectFormat.Name))
toPath = fmt.Sprintf("/forge/users/%s/projects/%s", userFormat.UserName, projectFormat.Name)
}
log.Trace("======= mirror %s => %s", fromPath, toPath)
output, err := runApp(ctx,
"f3", "mirror",
"--from-type", f3_filesystem_options.Name,
"--from-path", fromPath,
"--from-filesystem-directory", fixtureOptions.(f3_options.URLInterface).GetURL(),
"--to-type", options.Name,
"--to-path", toPath,
)
assert.NoError(t, err)
log.Trace("======= assert")
require.Contains(t, output, fmt.Sprintf("mirror %s", fromPath))
projects.List(ctx)
require.NotEmpty(t, projects.GetChildren())
log.Trace("======= project %s", projects.GetChildren()[0])
}