F3: Gitea driver and CLI
user, topic, project, label, milestone, repository, pull_request, release, asset, comment, reaction, review providers Signed-off-by: Earl Warren <contact@earl-warren.org>forgejo-f3
parent
e302cf89d8
commit
c88963261c
|
@ -97,6 +97,9 @@ issues:
|
|||
- gosec
|
||||
- unparam
|
||||
- staticcheck
|
||||
- path: services/f3/driver/driver.go
|
||||
linters:
|
||||
- typecheck
|
||||
- path: models/migrations/v
|
||||
linters:
|
||||
- gocyclo
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/services/f3/util"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3"
|
||||
f3_format "lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
)
|
||||
|
||||
var CmdF3 = cli.Command{
|
||||
Name: "f3",
|
||||
Usage: "Friendly Forge Format (F3) format export/import.",
|
||||
Description: "Import or export a repository from or to the Friendly Forge Format (F3) format.",
|
||||
Action: runF3,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "directory",
|
||||
Value: "./f3",
|
||||
Usage: "Path of the directory where the F3 dump is stored",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "user",
|
||||
Value: "",
|
||||
Usage: "The name of the user who owns the repository",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repository",
|
||||
Value: "",
|
||||
Usage: "The name of the repository",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-pull-request",
|
||||
Usage: "Do not dump pull requests",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "import",
|
||||
Usage: "Import from the directory",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "export",
|
||||
Usage: "Export to the directory",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runF3(ctx *cli.Context) error {
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(stdCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := git.InitSimple(stdCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RunF3(stdCtx, ctx)
|
||||
}
|
||||
|
||||
func RunF3(stdCtx context.Context, ctx *cli.Context) error {
|
||||
doer, err := user_model.GetAdminUser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
features := gof3.AllFeatures
|
||||
if ctx.Bool("no-pull-request") {
|
||||
features.PullRequests = false
|
||||
}
|
||||
|
||||
gitea := util.GiteaForgeRoot(stdCtx, features, doer)
|
||||
f3 := util.F3ForgeRoot(stdCtx, features, ctx.String("directory"))
|
||||
|
||||
if ctx.Bool("export") {
|
||||
gitea.Forge.Users.List()
|
||||
user := gitea.Forge.Users.GetFromFormat(&f3_format.User{UserName: ctx.String("user")})
|
||||
if user.IsNil() {
|
||||
return fmt.Errorf("%s is not a known user", ctx.String("user"))
|
||||
}
|
||||
|
||||
user.Projects.List()
|
||||
project := user.Projects.GetFromFormat(&f3_format.Project{Name: ctx.String("repository")})
|
||||
if project.IsNil() {
|
||||
return fmt.Errorf("%s/%s is not a known repository", ctx.String("user"), ctx.String("repository"))
|
||||
}
|
||||
|
||||
f3.Forge.Mirror(gitea.Forge, user, project)
|
||||
fmt.Println("exported")
|
||||
} else if ctx.Bool("import") {
|
||||
gitea.Forge.Mirror(f3.Forge)
|
||||
fmt.Println("imported")
|
||||
} else {
|
||||
return fmt.Errorf("either --import or --export must be specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -2337,6 +2337,15 @@ ROUTER = console
|
|||
;; If a domain is allowed by ALLOWED_DOMAINS, this option will be ignored.
|
||||
;ALLOW_LOCALNETWORKS = false
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;[F3]
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; Enable/Disable Friendly Forge Format (F3)
|
||||
;ENABLED = true
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;[federation]
|
||||
|
|
6
go.mod
6
go.mod
|
@ -4,7 +4,7 @@ go 1.18
|
|||
|
||||
require (
|
||||
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b
|
||||
code.gitea.io/sdk/gitea v0.15.1
|
||||
code.gitea.io/sdk/gitea v0.15.1-0.20220915214501-aef4e5e2bd47
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||
gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681
|
||||
gitea.com/go-chi/cache v0.2.0
|
||||
|
@ -103,6 +103,7 @@ require (
|
|||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lab.forgefriends.org/friendlyforgeformat/gof3 v0.0.0-20221129111956-573e4920cf5b
|
||||
mvdan.cc/xurls/v2 v2.4.0
|
||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
xorm.io/builder v0.3.11
|
||||
|
@ -158,6 +159,7 @@ require (
|
|||
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
|
@ -193,6 +195,7 @@ require (
|
|||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.2-0.20210511102531-373a877eec92 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.1 // indirect
|
||||
|
@ -283,6 +286,7 @@ require (
|
|||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220516143420-24438e51023a // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
|
|
14
go.sum
14
go.sum
|
@ -64,12 +64,11 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
|
|||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
|
||||
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
||||
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b h1:uv9a8eGSdQ8Dr4HyUcuHFfDsk/QuwO+wf+Y99RYdxY0=
|
||||
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
||||
code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
|
||||
code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
|
||||
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
|
||||
code.gitea.io/sdk/gitea v0.15.1-0.20220915214501-aef4e5e2bd47 h1:e9J9QdBk4NbdskZyGmJcaUGC/agG0UHMGyMrjgPb+6Q=
|
||||
code.gitea.io/sdk/gitea v0.15.1-0.20220915214501-aef4e5e2bd47/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
|
||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
|
||||
contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
|
||||
|
@ -377,6 +376,8 @@ github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
|
||||
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
|
@ -489,6 +490,7 @@ github.com/go-enry/go-enry/v2 v2.8.3 h1:BwvNrN58JqBJhyyVdZSl5QD3xoxEEGYUrRyPh31F
|
|||
github.com/go-enry/go-enry/v2 v2.8.3/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
|
||||
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8=
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||
|
@ -1600,12 +1602,14 @@ golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWP
|
|||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891 h1:WhEPFM1Ck5gaKybeSWvzI7Y/cd8K9K5tJGRxXMACOBA=
|
||||
|
@ -1621,6 +1625,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp v0.0.0-20220516143420-24438e51023a h1:tiLLxEjKNE6Hrah/Dp/cyHvsyjDLcMFSocOHO5XDmOM=
|
||||
golang.org/x/exp v0.0.0-20220516143420-24438e51023a/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -2255,6 +2261,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
|
|||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
||||
lab.forgefriends.org/friendlyforgeformat/gof3 v0.0.0-20221129111956-573e4920cf5b h1:0RwrwwyefLzAYPsmCxQqURlChCg6tW0y17g1PhPrm2Y=
|
||||
lab.forgefriends.org/friendlyforgeformat/gof3 v0.0.0-20221129111956-573e4920cf5b/go.mod h1:v2t/aa0w224NzBcx1mdzuxJSRWPcaaanQRhV43v7+yw=
|
||||
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
|
|
1
main.go
1
main.go
|
@ -75,6 +75,7 @@ arguments - which can alternatively be run by running the subcommand web.`
|
|||
cmd.CmdDocs,
|
||||
cmd.CmdDumpRepository,
|
||||
cmd.CmdRestoreRepository,
|
||||
cmd.CmdF3,
|
||||
}
|
||||
// Now adjust these commands to add our global configuration options
|
||||
|
||||
|
|
|
@ -229,6 +229,21 @@ func GetRepoTopicByName(ctx context.Context, repoID int64, topicName string) (*T
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// GetRepoTopicByID retrieves topic from ID for a repo if it exist
|
||||
func GetRepoTopicByID(ctx context.Context, repoID, topicID int64) (*Topic, error) {
|
||||
cond := builder.NewCond()
|
||||
var topic Topic
|
||||
cond = cond.And(builder.Eq{"repo_topic.repo_id": repoID}).And(builder.Eq{"topic.id": topicID})
|
||||
sess := db.GetEngine(ctx).Table("topic").Where(cond)
|
||||
sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id")
|
||||
if has, err := sess.Select("topic.*").Get(&topic); err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrTopicNotExist{""}
|
||||
}
|
||||
return &topic, nil
|
||||
}
|
||||
|
||||
// AddTopic adds a topic name to a repository (if it does not already have it)
|
||||
func AddTopic(repoID int64, topicName string) (*Topic, error) {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
|
|
|
@ -102,7 +102,7 @@ func ToPullReviewCommentList(ctx context.Context, review *issues_model.Review, d
|
|||
Path: comment.TreePath,
|
||||
CommitID: comment.CommitSHA,
|
||||
OrigCommitID: comment.OldRef,
|
||||
DiffHunk: patch2diff(comment.Patch),
|
||||
DiffHunk: Patch2diff(comment.Patch),
|
||||
HTMLURL: comment.HTMLURL(),
|
||||
HTMLPullURL: review.Issue.HTMLURL(),
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ func ToPullReviewCommentList(ctx context.Context, review *issues_model.Review, d
|
|||
return apiComments, nil
|
||||
}
|
||||
|
||||
func patch2diff(patch string) string {
|
||||
func Patch2diff(patch string) string {
|
||||
split := strings.Split(patch, "\n@@")
|
||||
if len(split) == 2 {
|
||||
return "@@" + split[1]
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
// Friendly Forge Format (F3) settings
|
||||
var (
|
||||
F3 = struct {
|
||||
Enabled bool
|
||||
}{
|
||||
Enabled: true,
|
||||
}
|
||||
)
|
||||
|
||||
func newF3Service() {
|
||||
if err := Cfg.Section("F3").MapTo(&F3); err != nil {
|
||||
log.Fatal("Failed to map F3 settings: %v", err)
|
||||
}
|
||||
}
|
|
@ -1340,6 +1340,7 @@ func NewServices() {
|
|||
newProject()
|
||||
newMimeTypeMap()
|
||||
newFederationService()
|
||||
newF3Service()
|
||||
}
|
||||
|
||||
// NewServicesForInstall initializes the services for install
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
This software is copyrighted. Unlimited copying and redistribution
|
||||
of this package and/or its individual files are permitted
|
||||
as long as there are no modifications. Modifications, and
|
||||
redistribution of modifications, are also permitted, but
|
||||
only if the resulting package and/or files are renamed.
|
|
@ -0,0 +1,15 @@
|
|||
Copyright (c) 1995 David Nugent <davidn@blaze.net.au>
|
||||
All rights reserved.
|
||||
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, is permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice immediately at the beginning of the file, without modification, this list of conditions, and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. This work was done expressly for inclusion into FreeBSD. Other use is permitted provided this notation is included.
|
||||
|
||||
4. Absolutely no warranty of function or purpose is made by the author David Nugent.
|
||||
|
||||
5. Modifications may be freely made to this file providing the above conditions are met.
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/services/attachment"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type Asset struct {
|
||||
repo_model.Attachment
|
||||
DownloadFunc func() io.ReadCloser
|
||||
}
|
||||
|
||||
func AssetConverter(f *repo_model.Attachment) *Asset {
|
||||
return &Asset{
|
||||
Attachment: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Asset) GetID() int64 {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
func (o *Asset) SetID(id int64) {
|
||||
o.ID = id
|
||||
}
|
||||
|
||||
func (o *Asset) IsNil() bool {
|
||||
return o.ID == 0
|
||||
}
|
||||
|
||||
func (o *Asset) Equals(other *Asset) bool {
|
||||
return o.Name == other.Name
|
||||
}
|
||||
|
||||
func (o *Asset) ToFormat() *format.ReleaseAsset {
|
||||
return &format.ReleaseAsset{
|
||||
Common: format.Common{Index: o.ID},
|
||||
Name: o.Name,
|
||||
Size: int(o.Size),
|
||||
DownloadCount: int(o.DownloadCount),
|
||||
Created: o.CreatedUnix.AsTime(),
|
||||
DownloadURL: o.DownloadURL(),
|
||||
DownloadFunc: o.DownloadFunc,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Asset) FromFormat(asset *format.ReleaseAsset) {
|
||||
*o = Asset{
|
||||
Attachment: repo_model.Attachment{
|
||||
ID: asset.GetID(),
|
||||
Name: asset.Name,
|
||||
Size: int64(asset.Size),
|
||||
DownloadCount: int64(asset.DownloadCount),
|
||||
CreatedUnix: timeutil.TimeStamp(asset.Created.Unix()),
|
||||
},
|
||||
DownloadFunc: asset.DownloadFunc,
|
||||
}
|
||||
}
|
||||
|
||||
type AssetProvider struct {
|
||||
g *Gitea
|
||||
}
|
||||
|
||||
func (o *AssetProvider) GetReferences(asset *Asset) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *AssetProvider) ToFormat(asset *Asset) *format.ReleaseAsset {
|
||||
httpClient := o.g.GetNewMigrationHTTPClient()()
|
||||
a := asset.ToFormat()
|
||||
a.DownloadFunc = func() io.ReadCloser {
|
||||
o.g.GetLogger().Debug("download from %s", asset.DownloadURL())
|
||||
req, err := http.NewRequest("GET", asset.DownloadURL(), nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("while downloading %s %w", asset.DownloadURL(), err))
|
||||
}
|
||||
|
||||
// resp.Body is closed by the consumer
|
||||
return resp.Body
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (o *AssetProvider) FromFormat(p *format.ReleaseAsset) *Asset {
|
||||
var asset Asset
|
||||
asset.FromFormat(p)
|
||||
return &asset
|
||||
}
|
||||
|
||||
func (o *AssetProvider) ProcessObject(user *User, project *Project, release *Release, asset *Asset) {
|
||||
}
|
||||
|
||||
func (o *AssetProvider) GetObjects(user *User, project *Project, release *Release, page int) []*Asset {
|
||||
if page > 1 {
|
||||
return []*Asset{}
|
||||
}
|
||||
r, err := repo_model.GetReleaseByID(o.g.ctx, release.GetID())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := r.LoadAttributes(o.g.ctx); err != nil {
|
||||
panic(fmt.Errorf("error while listing assets: %v", err))
|
||||
}
|
||||
|
||||
return util.ConvertMap[*repo_model.Attachment, *Asset](r.Attachments, AssetConverter)
|
||||
}
|
||||
|
||||
func (o *AssetProvider) Get(user *User, project *Project, release *Release, exemplar *Asset) *Asset {
|
||||
id := exemplar.GetID()
|
||||
asset, err := repo_model.GetAttachmentByID(o.g.ctx, id)
|
||||
if repo_model.IsErrAttachmentNotExist(err) {
|
||||
return &Asset{}
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return AssetConverter(asset)
|
||||
}
|
||||
|
||||
func (o *AssetProvider) Put(user *User, project *Project, release *Release, asset *Asset) *Asset {
|
||||
asset.ID = 0
|
||||
asset.UploaderID = user.GetID()
|
||||
asset.RepoID = project.GetID()
|
||||
asset.ReleaseID = release.GetID()
|
||||
asset.UUID = uuid.New().String()
|
||||
|
||||
download := asset.DownloadFunc()
|
||||
defer download.Close()
|
||||
a, err := attachment.NewAttachment(&asset.Attachment, download)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o.Get(user, project, release, AssetConverter(a))
|
||||
}
|
||||
|
||||
func (o *AssetProvider) Delete(user *User, project *Project, release *Release, asset *Asset) *Asset {
|
||||
a := o.Get(user, project, release, asset)
|
||||
if !a.IsNil() {
|
||||
err := repo_model.DeleteAttachment(&a.Attachment, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"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"
|
||||
comment_service "code.gitea.io/gitea/services/comments"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type Comment struct {
|
||||
issues_model.Comment
|
||||
}
|
||||
|
||||
func CommentConverter(f *issues_model.Comment) *Comment {
|
||||
return &Comment{
|
||||
Comment: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Comment) GetID() int64 {
|
||||
return o.Comment.ID
|
||||
}
|
||||
|
||||
func (o *Comment) SetID(id int64) {
|
||||
o.Comment.ID = id
|
||||
}
|
||||
|
||||
func (o *Comment) IsNil() bool {
|
||||
return o.ID == 0
|
||||
}
|
||||
|
||||
func (o *Comment) Equals(other *Comment) bool {
|
||||
return o.Comment.ID == other.Comment.ID
|
||||
}
|
||||
|
||||
func (o *Comment) ToFormat() *format.Comment {
|
||||
return &format.Comment{
|
||||
Common: format.Common{Index: o.Comment.ID},
|
||||
IssueIndex: o.Comment.IssueID,
|
||||
PosterID: o.Comment.PosterID,
|
||||
Content: o.Comment.Content,
|
||||
Created: o.Comment.CreatedUnix.AsTime(),
|
||||
Updated: o.Comment.UpdatedUnix.AsTime(),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Comment) FromFormat(comment *format.Comment) {
|
||||
*o = Comment{
|
||||
Comment: issues_model.Comment{
|
||||
ID: comment.Index,
|
||||
PosterID: comment.PosterID,
|
||||
Poster: &user_model.User{
|
||||
ID: comment.PosterID,
|
||||
},
|
||||
Content: comment.Content,
|
||||
CreatedUnix: timeutil.TimeStamp(comment.Created.Unix()),
|
||||
UpdatedUnix: timeutil.TimeStamp(comment.Updated.Unix()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type CommentProvider struct {
|
||||
g *Gitea
|
||||
}
|
||||
|
||||
func (o *CommentProvider) GetReferences(comment *Comment) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CommentProvider) ToFormat(comment *Comment) *format.Comment {
|
||||
return comment.ToFormat()
|
||||
}
|
||||
|
||||
func (o *CommentProvider) FromFormat(f *format.Comment) *Comment {
|
||||
var comment Comment
|
||||
comment.FromFormat(f)
|
||||
return &comment
|
||||
}
|
||||
|
||||
func (o *CommentProvider) GetObjects(user *User, project *Project, commentable common.ContainerObjectInterface, page int) []*Comment {
|
||||
comments, err := issues_model.FindComments(o.g.ctx, &issues_model.FindCommentsOptions{
|
||||
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
|
||||
RepoID: project.GetID(),
|
||||
IssueID: commentable.GetID(),
|
||||
Type: issues_model.CommentTypeComment,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error while listing comment: %v", err))
|
||||
}
|
||||
|
||||
return util.ConvertMap[*issues_model.Comment, *Comment](comments, CommentConverter)
|
||||
}
|
||||
|
||||
func (o *CommentProvider) ProcessObject(user *User, project *Project, commentable common.ContainerObjectInterface, comment *Comment) {
|
||||
if err := comment.LoadIssue(o.g.ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := comment.LoadPoster(o.g.ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *CommentProvider) Get(user *User, project *Project, commentable common.ContainerObjectInterface, comment *Comment) *Comment {
|
||||
id := comment.GetID()
|
||||
c, err := issues_model.GetCommentByID(o.g.ctx, id)
|
||||
if issues_model.IsErrCommentNotExist(err) {
|
||||
return &Comment{}
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
co := CommentConverter(c)
|
||||
o.ProcessObject(user, project, commentable, co)
|
||||
return co
|
||||
}
|
||||
|
||||
func (o *CommentProvider) Put(user *User, project *Project, commentable common.ContainerObjectInterface, comment *Comment) *Comment {
|
||||
var issue *issues_model.Issue
|
||||
switch c := commentable.(type) {
|
||||
case *PullRequest:
|
||||
issue = c.PullRequest.Issue
|
||||
case *Issue:
|
||||
issue = &c.Issue
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected type %T", commentable))
|
||||
}
|
||||
c, err := comment_service.CreateIssueComment(o.g.ctx, o.g.GetDoer(), &project.Repository, issue, comment.Content, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o.Get(user, project, commentable, CommentConverter(c))
|
||||
}
|
||||
|
||||
func (o *CommentProvider) Delete(user *User, project *Project, commentable common.ContainerObjectInterface, comment *Comment) *Comment {
|
||||
c := o.Get(user, project, commentable, comment)
|
||||
if !c.IsNil() {
|
||||
err := issues_model.DeleteComment(o.g.ctx, &c.Comment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/driver"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
gof3.Options
|
||||
|
||||
Doer *user_model.User
|
||||
}
|
||||
|
||||
type Gitea struct {
|
||||
perPage int
|
||||
ctx context.Context
|
||||
options *Options
|
||||
}
|
||||
|
||||
func (o *Gitea) GetPerPage() int {
|
||||
return o.perPage
|
||||
}
|
||||
|
||||
func (o *Gitea) GetOptions() gof3.OptionsInterface {
|
||||
return o.options
|
||||
}
|
||||
|
||||
func (o *Gitea) SetOptions(options gof3.OptionsInterface) {
|
||||
var ok bool
|
||||
o.options, ok = options.(*Options)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("unexpected type %T", options))
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Gitea) GetLogger() *gof3.Logger {
|
||||
return o.GetOptions().GetLogger()
|
||||
}
|
||||
|
||||
func (o *Gitea) Init(options gof3.OptionsInterface) {
|
||||
o.SetOptions(options)
|
||||
o.perPage = setting.ItemsPerPage
|
||||
}
|
||||
|
||||
func (o *Gitea) GetDirectory() string {
|
||||
return o.options.GetDirectory()
|
||||
}
|
||||
|
||||
func (o *Gitea) GetDoer() *user_model.User {
|
||||
return o.options.Doer
|
||||
}
|
||||
|
||||
func (o *Gitea) GetNewMigrationHTTPClient() gof3.NewMigrationHTTPClientFun {
|
||||
return migrations.NewMigrationHTTPClient
|
||||
}
|
||||
|
||||
func (o *Gitea) SupportGetRepoComments() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *Gitea) SetContext(ctx context.Context) {
|
||||
o.ctx = ctx
|
||||
}
|
||||
|
||||
func (o *Gitea) GetProvider(name string, parent common.ProviderInterface) common.ProviderInterface {
|
||||
var parentImpl any
|
||||
if parent != nil {
|
||||
parentImpl = parent.GetImplementation()
|
||||
}
|
||||
switch name {
|
||||
case driver.ProviderUser:
|
||||
return driver.NewProvider[UserProvider, *UserProvider, User, *User, format.User, *format.User](&UserProvider{g: o})
|
||||
case driver.ProviderProject:
|
||||
return driver.NewProviderWithParentOne[ProjectProvider, *ProjectProvider, Project, *Project, format.Project, *format.Project, User, *User](&ProjectProvider{g: o})
|
||||
case driver.ProviderMilestone:
|
||||
return driver.NewProviderWithParentOneTwo[MilestoneProvider, *MilestoneProvider, Milestone, *Milestone, format.Milestone, *format.Milestone, User, *User, Project, *Project](&MilestoneProvider{g: o, project: parentImpl.(*ProjectProvider)})
|
||||
case driver.ProviderIssue:
|
||||
return driver.NewProviderWithParentOneTwo[IssueProvider, *IssueProvider, Issue, *Issue, format.Issue, *format.Issue, User, *User, Project, *Project](&IssueProvider{g: o, project: parentImpl.(*ProjectProvider)})
|
||||
case driver.ProviderPullRequest:
|
||||
return driver.NewProviderWithParentOneTwo[PullRequestProvider, *PullRequestProvider, PullRequest, *PullRequest, format.PullRequest, *format.PullRequest, User, *User, Project, *Project](&PullRequestProvider{g: o, project: parentImpl.(*ProjectProvider)})
|
||||
case driver.ProviderReview:
|
||||
return driver.NewProviderWithParentOneTwoThree[ReviewProvider, *ReviewProvider, Review, *Review, format.Review, *format.Review, User, *User, Project, *Project, PullRequest, *PullRequest](&ReviewProvider{g: o})
|
||||
case driver.ProviderRepository:
|
||||
return driver.NewProviderWithParentOneTwo[RepositoryProvider, *RepositoryProvider, Repository, *Repository, format.Repository, *format.Repository, User, *User, Project, *Project](&RepositoryProvider{g: o})
|
||||
case driver.ProviderTopic:
|
||||
return driver.NewProviderWithParentOneTwo[TopicProvider, *TopicProvider, Topic, *Topic, format.Topic, *format.Topic, User, *User, Project, *Project](&TopicProvider{g: o})
|
||||
case driver.ProviderLabel:
|
||||
return driver.NewProviderWithParentOneTwo[LabelProvider, *LabelProvider, Label, *Label, format.Label, *format.Label, User, *User, Project, *Project](&LabelProvider{g: o, project: parentImpl.(*ProjectProvider)})
|
||||
case driver.ProviderRelease:
|
||||
return driver.NewProviderWithParentOneTwo[ReleaseProvider, *ReleaseProvider, Release, *Release, format.Release, *format.Release, User, *User, Project, *Project](&ReleaseProvider{g: o})
|
||||
case driver.ProviderAsset:
|
||||
return driver.NewProviderWithParentOneTwoThree[AssetProvider, *AssetProvider, Asset, *Asset, format.ReleaseAsset, *format.ReleaseAsset, User, *User, Project, *Project, Release, *Release](&AssetProvider{g: o})
|
||||
case driver.ProviderComment:
|
||||
return driver.NewProviderWithParentOneTwoThreeInterface[CommentProvider, *CommentProvider, Comment, *Comment, format.Comment, *format.Comment, User, *User, Project, *Project](&CommentProvider{g: o})
|
||||
case driver.ProviderReaction:
|
||||
return driver.NewProviderWithParentOneTwoRest[ReactionProvider, *ReactionProvider, Reaction, *Reaction, format.Reaction, *format.Reaction, User, *User, Project, *Project](&ReactionProvider{g: o})
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown provider name %s", name))
|
||||
}
|
||||
}
|
||||
|
||||
func (o Gitea) Finish() {
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"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"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type Issue struct {
|
||||
issues_model.Issue
|
||||
}
|
||||
|
||||
func IssueConverter(f *issues_model.Issue) *Issue {
|
||||
return &Issue{
|
||||
Issue: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Issue) GetID() int64 {
|
||||
return o.Index
|
||||
}
|
||||
|
||||
func (o *Issue) SetID(id int64) {
|
||||
o.Index = id
|
||||
}
|
||||
|
||||
func (o *Issue) IsNil() bool {
|
||||
return o.Index == 0
|
||||
}
|
||||
|
||||
func (o *Issue) Equals(other *Issue) bool {
|
||||
return o.Index == other.Index
|
||||
}
|
||||
|
||||
func (o *Issue) ToFormat() *format.Issue {
|
||||
var milestone string
|
||||
if o.Milestone != nil {
|
||||
milestone = o.Milestone.Name
|
||||
}
|
||||
|
||||
labels := make([]string, 0, len(o.Labels))
|
||||
for _, label := range o.Labels {
|
||||
labels = append(labels, label.Name)
|
||||
}
|
||||
|
||||
var assignees []string
|
||||
for i := range o.Assignees {
|
||||
assignees = append(assignees, o.Assignees[i].Name)
|
||||
}
|
||||
|
||||
return &format.Issue{
|
||||
Common: format.Common{Index: o.Index},
|
||||
Title: o.Title,
|
||||
PosterID: o.Poster.ID,
|
||||
Content: o.Content,
|
||||
Milestone: milestone,
|
||||
State: string(o.State()),
|
||||
Created: o.CreatedUnix.AsTime(),
|
||||
Updated: o.UpdatedUnix.AsTime(),
|
||||
Closed: o.ClosedUnix.AsTimePtr(),
|
||||
IsLocked: o.IsLocked,
|
||||
Labels: labels,
|
||||
Assignees: assignees,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Issue) FromFormat(issue *format.Issue) {
|
||||
labels := make([]*issues_model.Label, 0, len(issue.Labels))
|
||||
for _, label := range issue.Labels {
|
||||
labels = append(labels, &issues_model.Label{Name: label})
|
||||
}
|
||||
|
||||
assignees := make([]*user_model.User, 0, len(issue.Assignees))
|
||||
for _, a := range issue.Assignees {
|
||||
assignees = append(assignees, &user_model.User{
|
||||
Name: a,
|
||||
})
|
||||
}
|
||||
|
||||
*o = Issue{
|
||||
Issue: issues_model.Issue{
|
||||
Title: issue.Title,
|
||||
Index: issue.Index,
|
||||
Poster: &user_model.User{
|
||||
ID: issue.PosterID,
|
||||
},
|
||||
Content: issue.Content,
|
||||
Milestone: &issues_model.Milestone{
|
||||
Name: issue.Milestone,
|
||||
},
|
||||
IsClosed: issue.State == "closed",
|
||||
CreatedUnix: timeutil.TimeStamp(issue.Created.Unix()),
|
||||
UpdatedUnix: timeutil.TimeStamp(issue.Updated.Unix()),
|
||||
ClosedUnix: timeutil.TimeStamp(issue.Closed.Unix()),
|
||||
IsLocked: issue.IsLocked,
|
||||
Labels: labels,
|
||||
Assignees: assignees,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type IssueProvider struct {
|
||||
g *Gitea
|
||||
project *ProjectProvider
|
||||
}
|
||||
|
||||
func (o *IssueProvider) GetReferences(issue *Issue) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *IssueProvider) ToFormat(issue *Issue) *format.Issue {
|
||||
return issue.ToFormat()
|
||||
}
|
||||
|
||||
func (o *IssueProvider) FromFormat(i *format.Issue) *Issue {
|
||||
var issue Issue
|
||||
issue.FromFormat(i)
|
||||
if i.Milestone != "" {
|
||||
issue.Milestone.ID = o.project.milestones.GetID(issue.Milestone.Name)
|
||||
}
|
||||
for _, label := range issue.Labels {
|
||||
label.ID = o.project.labels.GetID(label.Name)
|
||||
}
|
||||
return &issue
|
||||
}
|
||||
|
||||
func (o *IssueProvider) GetObjects(user *User, project *Project, page int) []*Issue {
|
||||
issues, err := issues_model.Issues(o.g.ctx, &issues_model.IssuesOptions{
|
||||
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
|
||||
RepoID: project.GetID(),
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error while listing issues: %v", err))
|
||||
}
|
||||
|
||||
return util.ConvertMap[*issues_model.Issue, *Issue](issues, IssueConverter)
|
||||
}
|
||||
|
||||
func (o *IssueProvider) ProcessObject(user *User, project *Project, issue *Issue) {
|
||||
if err := (&issue.Issue).LoadAttributes(o.g.ctx); err != nil {
|
||||
panic(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *IssueProvider) Get(user *User, project *Project, exemplar *Issue) *Issue {
|
||||
id := exemplar.GetID()
|
||||
issue, err := issues_model.GetIssueByIndex(project.GetID(), id)
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
return &Issue{}
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
i := IssueConverter(issue)
|
||||
o.ProcessObject(user, project, i)
|
||||
return i
|
||||
}
|
||||
|
||||
func (o *IssueProvider) Put(user *User, project *Project, issue *Issue) *Issue {
|
||||
i := issue.Issue
|
||||
i.RepoID = project.GetID()
|
||||
labels := make([]int64, 0, len(i.Labels))
|
||||
for _, label := range i.Labels {
|
||||
labels = append(labels, label.ID)
|
||||
}
|
||||
|
||||
if err := issues_model.NewIssue(&project.Repository, &i, labels, []string{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o.Get(user, project, IssueConverter(&i))
|
||||
}
|
||||
|
||||
func (o *IssueProvider) Delete(user *User, project *Project, issue *Issue) *Issue {
|
||||
m := o.Get(user, project, issue)
|
||||
if !m.IsNil() {
|
||||
repoPath := repo_model.RepoPath(user.Name, project.Name)
|
||||
gitRepo, err := git.OpenRepository(o.g.ctx, repoPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
if err := issue_service.DeleteIssue(o.g.GetDoer(), gitRepo, &issue.Issue); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type Label struct {
|
||||
issues_model.Label
|
||||
}
|
||||
|
||||
func LabelConverter(f *issues_model.Label) *Label {
|
||||
return &Label{
|
||||
Label: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Label) GetID() int64 {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
func (o Label) GetName() string {
|
||||
return o.Name
|
||||
}
|
||||
|
||||
func (o *Label) SetID(id int64) {
|
||||
o.ID = id
|
||||
}
|
||||
|
||||
func (o *Label) IsNil() bool {
|
||||
return o.ID == 0
|
||||
}
|
||||
|
||||
func (o *Label) Equals(other *Label) bool {
|
||||
return o.Name == other.Name
|
||||
}
|
||||
|
||||
func (o *Label) ToFormat() *format.Label {
|
||||
return &format.Label{
|
||||
Common: format.Common{Index: o.ID},
|
||||
Name: o.Name,
|
||||
Color: o.Color,
|
||||
Description: o.Description,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Label) FromFormat(label *format.Label) {
|
||||
*o = Label{
|
||||
Label: issues_model.Label{
|
||||
ID: label.Index,
|
||||
Name: label.Name,
|
||||
Description: label.Description,
|
||||
Color: label.Color,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type LabelProvider struct {
|
||||
g *Gitea
|
||||
project *ProjectProvider
|
||||
}
|
||||
|
||||
func (o *LabelProvider) GetReferences(label *Label) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LabelProvider) ToFormat(label *Label) *format.Label {
|
||||
return label.ToFormat()
|
||||
}
|
||||
|
||||
func (o *LabelProvider) FromFormat(m *format.Label) *Label {
|
||||
var label Label
|
||||
label.FromFormat(m)
|
||||
return &label
|
||||
}
|
||||
|
||||
func (o *LabelProvider) GetObjects(user *User, project *Project, page int) []*Label {
|
||||
labels, err := issues_model.GetLabelsByRepoID(o.g.ctx, project.GetID(), "", db.ListOptions{Page: page, PageSize: o.g.perPage})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error while listing labels: %v", err))
|
||||
}
|
||||
|
||||
r := util.ConvertMap[*issues_model.Label, *Label](labels, LabelConverter)
|
||||
if o.project != nil {
|
||||
o.project.labels = util.NewNameIDMap[*Label](r)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (o *LabelProvider) ProcessObject(user *User, project *Project, label *Label) {
|
||||
}
|
||||
|
||||
func (o *LabelProvider) Get(user *User, project *Project, exemplar *Label) *Label {
|
||||
id := exemplar.GetID()
|
||||
label, err := issues_model.GetLabelInRepoByID(o.g.ctx, project.GetID(), id)
|
||||
if issues_model.IsErrRepoLabelNotExist(err) {
|
||||
return &Label{}
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return LabelConverter(label)
|
||||
}
|
||||
|
||||
func (o *LabelProvider) Put(user *User, project *Project, label *Label) *Label {
|
||||
l := label.Label
|
||||
l.RepoID = project.GetID()
|
||||
if err := issues_model.NewLabel(o.g.ctx, &l); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o.Get(user, project, LabelConverter(&l))
|
||||
}
|
||||
|
||||
func (o *LabelProvider) Delete(user *User, project *Project, label *Label) *Label {
|
||||
l := o.Get(user, project, label)
|
||||
if !l.IsNil() {
|
||||
if err := issues_model.DeleteLabel(project.GetID(), l.GetID()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type Milestone struct {
|
||||
issues_model.Milestone
|
||||
}
|
||||
|
||||
func MilestoneConverter(f *issues_model.Milestone) *Milestone {
|
||||
return &Milestone{
|
||||
Milestone: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Milestone) GetID() int64 {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
func (o Milestone) GetName() string {
|
||||
return o.Name
|
||||
}
|
||||
|
||||
func (o *Milestone) SetID(id int64) {
|
||||
o.ID = id
|
||||
}
|
||||
|
||||
func (o *Milestone) IsNil() bool {
|
||||
return o.ID == 0
|
||||
}
|
||||
|
||||
func (o *Milestone) Equals(other *Milestone) bool {
|
||||
return o.Name == other.Name
|
||||
}
|
||||
|
||||
func (o *Milestone) ToFormat() *format.Milestone {
|
||||
milestone := &format.Milestone{
|
||||
Common: format.Common{Index: o.ID},
|
||||
Title: o.Name,
|
||||
Description: o.Content,
|
||||
Created: o.CreatedUnix.AsTime(),
|
||||
Updated: o.UpdatedUnix.AsTimePtr(),
|
||||
State: string(o.State()),
|
||||
}
|
||||
if o.IsClosed {
|
||||
milestone.Closed = o.ClosedDateUnix.AsTimePtr()
|
||||
}
|
||||
if o.DeadlineUnix.Year() < 9999 {
|
||||
milestone.Deadline = o.DeadlineUnix.AsTimePtr()
|
||||
}
|
||||
return milestone
|
||||
}
|
||||
|
||||
func (o *Milestone) FromFormat(milestone *format.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 = Milestone{
|
||||
issues_model.Milestone{
|
||||
ID: milestone.Index,
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type MilestoneProvider struct {
|
||||
g *Gitea
|
||||
project *ProjectProvider
|
||||
}
|
||||
|
||||
func (o *MilestoneProvider) GetReferences(milestone *Milestone) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *MilestoneProvider) ToFormat(milestone *Milestone) *format.Milestone {
|
||||
return milestone.ToFormat()
|
||||
}
|
||||
|
||||
func (o *MilestoneProvider) FromFormat(m *format.Milestone) *Milestone {
|
||||
var milestone Milestone
|
||||
milestone.FromFormat(m)
|
||||
return &milestone
|
||||
}
|
||||
|
||||
func (o *MilestoneProvider) GetObjects(user *User, project *Project, page int) []*Milestone {
|
||||
milestones, _, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{
|
||||
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
|
||||
RepoID: project.GetID(),
|
||||
State: api.StateAll,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error while listing milestones: %v", err))
|
||||
}
|
||||
|
||||
r := util.ConvertMap[*issues_model.Milestone, *Milestone](([]*issues_model.Milestone)(milestones), MilestoneConverter)
|
||||
if o.project != nil {
|
||||
o.project.milestones = util.NewNameIDMap[*Milestone](r)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (o *MilestoneProvider) ProcessObject(user *User, project *Project, milestone *Milestone) {
|
||||
}
|
||||
|
||||
func (o *MilestoneProvider) Get(user *User, project *Project, exemplar *Milestone) *Milestone {
|
||||
id := exemplar.GetID()
|
||||
milestone, err := issues_model.GetMilestoneByRepoID(o.g.ctx, project.GetID(), id)
|
||||
if issues_model.IsErrMilestoneNotExist(err) {
|
||||
return &Milestone{}
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return MilestoneConverter(milestone)
|
||||
}
|
||||
|
||||
func (o *MilestoneProvider) Put(user *User, project *Project, milestone *Milestone) *Milestone {
|
||||
m := milestone.Milestone
|
||||
m.RepoID = project.GetID()
|
||||
if err := issues_model.NewMilestone(&m); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o.Get(user, project, MilestoneConverter(&m))
|
||||
}
|
||||
|
||||
func (o *MilestoneProvider) Delete(user *User, project *Project, milestone *Milestone) *Milestone {
|
||||
m := o.Get(user, project, milestone)
|
||||
if !m.IsNil() {
|
||||
if err := issues_model.DeleteMilestoneByRepoID(project.GetID(), m.GetID()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
f3_util "lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type Project struct {
|
||||
repo_model.Repository
|
||||
}
|
||||
|
||||
func ProjectConverter(f *repo_model.Repository) *Project {
|
||||
return &Project{
|
||||
Repository: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Project) GetID() int64 {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
func (o *Project) SetID(id int64) {
|
||||
o.ID = id
|
||||
}
|
||||
|
||||
func (o *Project) IsNil() bool {
|
||||
return o.ID == 0
|
||||
}
|
||||
|
||||
func (o *Project) Equals(other *Project) bool {
|
||||
return (o.Name == other.Name)
|
||||
}
|
||||
|
||||
func (o *Project) ToFormat() *format.Project {
|
||||
return &format.Project{
|
||||
Common: format.Common{Index: o.ID},
|
||||
Name: o.Name,
|
||||
Owner: o.Owner.Name,
|
||||
IsPrivate: o.IsPrivate,
|
||||
Description: o.Description,
|
||||
CloneURL: repo_model.ComposeHTTPSCloneURL(o.Owner.Name, o.Name),
|
||||
OriginalURL: o.OriginalURL,
|
||||
DefaultBranch: o.DefaultBranch,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Project) FromFormat(project *format.Project) {
|
||||
*o = Project{
|
||||
Repository: repo_model.Repository{
|
||||
ID: project.Index,
|
||||
Name: project.Name,
|
||||
Owner: &user_model.User{
|
||||
Name: project.Owner,
|
||||
},
|
||||
IsPrivate: project.IsPrivate,
|
||||
Description: project.Description,
|
||||
OriginalURL: project.OriginalURL,
|
||||
DefaultBranch: project.DefaultBranch,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type ProjectProvider struct {
|
||||
g *Gitea
|
||||
milestones f3_util.NameIDMap
|
||||
labels f3_util.NameIDMap
|
||||
}
|
||||
|
||||
func (o *ProjectProvider) GetReferences(project *Project) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ProjectProvider) ToFormat(project *Project) *format.Project {
|
||||
return project.ToFormat()
|
||||
}
|
||||
|
||||
func (o *ProjectProvider) FromFormat(p *format.Project) *Project {
|
||||
var project Project
|
||||
project.FromFormat(p)
|
||||
return &project
|
||||
}
|
||||
|
||||
func (o *ProjectProvider) GetObjects(user *User, page int) []*Project {
|
||||
repoList, _, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{
|
||||
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
|
||||
Actor: &user.User,
|
||||
Private: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error while listing projects: %T %v", err, err))
|
||||
}
|
||||
if err := repoList.LoadAttributes(); err != nil {
|
||||
panic(nil)
|
||||
}
|
||||
return f3_util.ConvertMap[*repo_model.Repository, *Project](([]*repo_model.Repository)(repoList), ProjectConverter)
|
||||
}
|
||||
|
||||
func (o *ProjectProvider) ProcessObject(user *User, project *Project) {
|
||||
}
|
||||
|
||||
func (o *ProjectProvider) Get(user *User, exemplar *Project) *Project {
|
||||
var project *repo_model.Repository
|
||||
var err error
|
||||
if exemplar.GetID() > 0 {
|
||||
project, err = repo_model.GetRepositoryByIDCtx(o.g.ctx, exemplar.GetID())
|
||||
} else if exemplar.Name != "" {
|
||||
project, err = repo_model.GetRepositoryByName(user.GetID(), exemplar.Name)
|
||||
} else {
|
||||
panic("GetID() == 0 and ProjectName == \"\"")
|
||||
}
|
||||
if repo_model.IsErrRepoNotExist(err) {
|
||||
return &Project{}
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("project %v %w", exemplar, err))
|
||||
}
|
||||
if err := project.GetOwner(o.g.ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ProjectConverter(project)
|
||||
}
|
||||
|
||||
func (o *ProjectProvider) Put(user *User, project *Project) *Project {
|
||||
repo, err := repo_module.CreateRepository(o.g.GetDoer(), &user.User, repo_module.CreateRepoOptions{
|
||||
Name: project.Name,
|
||||
Description: project.Description,
|
||||
OriginalURL: project.OriginalURL,
|
||||
IsPrivate: project.IsPrivate,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o.Get(user, ProjectConverter(repo))
|
||||
}
|
||||
|
||||
func (o *ProjectProvider) Delete(user *User, project *Project) *Project {
|
||||
if project.IsNil() {
|
||||
return project
|
||||
}
|
||||
if project.ID > 0 {
|
||||
project = o.Get(user, project)
|
||||
}
|
||||
if !project.IsNil() {
|
||||
err := repo_service.DeleteRepository(o.g.ctx, o.g.GetDoer(), &project.Repository, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return project
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package 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"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
f3_gitea "lab.forgefriends.org/friendlyforgeformat/gof3/forges/gitea"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type PullRequest struct {
|
||||
issues_model.PullRequest
|
||||
FetchFunc func(repository string) string
|
||||
}
|
||||
|
||||
func PullRequestConverter(f *issues_model.PullRequest) *PullRequest {
|
||||
return &PullRequest{
|
||||
PullRequest: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o PullRequest) GetID() int64 {
|
||||
return o.Index
|
||||
}
|
||||
|
||||
func (o *PullRequest) SetID(id int64) {
|
||||
o.Index = id
|
||||
}
|
||||
|
||||
func (o *PullRequest) IsNil() bool {
|
||||
return o.Index == 0
|
||||
}
|
||||
|
||||
func (o *PullRequest) Equals(other *PullRequest) bool {
|
||||
return o.Issue.Title == other.Issue.Title
|
||||
}
|
||||
|
||||
func (o PullRequest) IsForkPullRequest() bool {
|
||||
return o.HeadRepoID != o.BaseRepoID
|
||||
}
|
||||
|
||||
func (o *PullRequest) ToFormat() *format.PullRequest {
|
||||
var milestone string
|
||||
if o.Issue.Milestone != nil {
|
||||
milestone = o.Issue.Milestone.Name
|
||||
}
|
||||
|
||||
labels := make([]string, 0, len(o.Issue.Labels))
|
||||
for _, label := range o.Issue.Labels {
|
||||
labels = append(labels, label.Name)
|
||||
}
|
||||
|
||||
var mergedTime *time.Time
|
||||
if o.HasMerged {
|
||||
mergedTime = o.MergedUnix.AsTimePtr()
|
||||
}
|
||||
|
||||
getSHA := func(repo *repo_model.Repository, branch string) string {
|
||||
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 c.ID.String()
|
||||
}
|
||||
|
||||
head := format.PullRequestBranch{
|
||||
CloneURL: o.HeadRepo.CloneLink().HTTPS,
|
||||
Ref: o.HeadBranch,
|
||||
SHA: getSHA(o.HeadRepo, o.HeadBranch),
|
||||
RepoName: o.HeadRepo.Name,
|
||||
OwnerName: o.HeadRepo.OwnerName,
|
||||
}
|
||||
|
||||
base := format.PullRequestBranch{
|
||||
CloneURL: o.BaseRepo.CloneLink().HTTPS,
|
||||
Ref: o.BaseBranch,
|
||||
SHA: getSHA(o.BaseRepo, o.BaseBranch),
|
||||
RepoName: o.BaseRepo.Name,
|
||||
OwnerName: o.BaseRepo.OwnerName,
|
||||
}
|
||||
|
||||
return &format.PullRequest{
|
||||
Common: format.Common{Index: o.Index},
|
||||
PosterID: o.Issue.Poster.ID,
|
||||
Title: o.Issue.Title,
|
||||
Content: o.Issue.Content,
|
||||
Milestone: milestone,
|
||||
State: string(o.Issue.State()),
|
||||
IsLocked: o.Issue.IsLocked,
|
||||
Created: o.Issue.CreatedUnix.AsTime(),
|
||||
Updated: o.Issue.UpdatedUnix.AsTime(),
|
||||
Closed: o.Issue.ClosedUnix.AsTimePtr(),
|
||||
Labels: labels,
|
||||
PatchURL: o.Issue.PatchURL(),
|
||||
Merged: o.HasMerged,
|
||||
MergedTime: mergedTime,
|
||||
MergeCommitSHA: o.MergedCommitID,
|
||||
Head: head,
|
||||
Base: base,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *PullRequest) FromFormat(pullRequest *format.PullRequest) {
|
||||
labels := make([]*issues_model.Label, 0, len(pullRequest.Labels))
|
||||
for _, label := range pullRequest.Labels {
|
||||
labels = append(labels, &issues_model.Label{Name: label})
|
||||
}
|
||||
|
||||
if pullRequest.Created.IsZero() {
|
||||
if pullRequest.Closed != nil {
|
||||
pullRequest.Created = *pullRequest.Closed
|
||||
} else if pullRequest.MergedTime != nil {
|
||||
pullRequest.Created = *pullRequest.MergedTime
|
||||
} else {
|
||||
pullRequest.Created = time.Now()
|
||||
}
|
||||
}
|
||||
if pullRequest.Updated.IsZero() {
|
||||
pullRequest.Updated = pullRequest.Created
|
||||
}
|
||||
|
||||
base, err := repo_model.GetRepositoryByOwnerAndName(pullRequest.Base.OwnerName, pullRequest.Base.RepoName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var head *repo_model.Repository
|
||||
if pullRequest.Head.RepoName == "" {
|
||||
head = base
|
||||
} else {
|
||||
head, err = repo_model.GetRepositoryByOwnerAndName(pullRequest.Head.OwnerName, pullRequest.Head.RepoName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
issue := issues_model.Issue{
|
||||
RepoID: base.ID,
|
||||
Repo: base,
|
||||
Title: pullRequest.Title,
|
||||
Index: pullRequest.Index,
|
||||
Content: pullRequest.Content,
|
||||
IsPull: true,
|
||||
IsClosed: pullRequest.State == "closed",
|
||||
IsLocked: pullRequest.IsLocked,
|
||||
Labels: labels,
|
||||
CreatedUnix: timeutil.TimeStamp(pullRequest.Created.Unix()),
|
||||
UpdatedUnix: timeutil.TimeStamp(pullRequest.Updated.Unix()),
|
||||
}
|
||||
|
||||
pr := issues_model.PullRequest{
|
||||
HeadRepoID: head.ID,
|
||||
HeadBranch: pullRequest.Head.Ref,
|
||||
BaseRepoID: base.ID,
|
||||
BaseBranch: pullRequest.Base.Ref,
|
||||
MergeBase: pullRequest.Base.SHA,
|
||||
Index: pullRequest.Index,
|
||||
HasMerged: pullRequest.Merged,
|
||||
|
||||
Issue: &issue,
|
||||
}
|
||||
|
||||
if pr.Issue.IsClosed && pullRequest.Closed != nil {
|
||||
pr.Issue.ClosedUnix = timeutil.TimeStamp(pullRequest.Closed.Unix())
|
||||
}
|
||||
if pr.HasMerged && pullRequest.MergedTime != nil {
|
||||
pr.MergedUnix = timeutil.TimeStamp(pullRequest.MergedTime.Unix())
|
||||
pr.MergedCommitID = pullRequest.MergeCommitSHA
|
||||
}
|
||||
|
||||
*o = PullRequest{
|
||||
PullRequest: pr,
|
||||
}
|
||||
}
|
||||
|
||||
type PullRequestProvider struct {
|
||||
g *Gitea
|
||||
project *ProjectProvider
|
||||
prHeadCache f3_gitea.PrHeadCache
|
||||
}
|
||||
|
||||
func (o *PullRequestProvider) GetReferences(pullRequest *PullRequest) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *PullRequestProvider) ToFormat(pullRequest *PullRequest) *format.PullRequest {
|
||||
return pullRequest.ToFormat()
|
||||
}
|
||||
|
||||
func (o *PullRequestProvider) FromFormat(pr *format.PullRequest) *PullRequest {
|
||||
var pullRequest PullRequest
|
||||
pullRequest.FromFormat(pr)
|
||||
return &pullRequest
|
||||
}
|
||||
|
||||
func (o *PullRequestProvider) Init() *PullRequestProvider {
|
||||
o.prHeadCache = make(f3_gitea.PrHeadCache)
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *PullRequestProvider) cleanupRemotes(repository string) {
|
||||
for remote := range o.prHeadCache {
|
||||
util.Command(o.g.ctx, "git", "-C", repository, "remote", "rm", remote)
|
||||
}
|
||||
o.prHeadCache = make(f3_gitea.PrHeadCache)
|
||||
}
|
||||
|
||||
func (o *PullRequestProvider) GetObjects(user *User, project *Project, page int) []*PullRequest {
|
||||
pullRequests, _, err := issues_model.PullRequests(project.GetID(), &issues_model.PullRequestsOptions{
|
||||
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
|
||||
State: string(api.StateAll),
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error while listing pullRequests: %v", err))
|
||||
}
|
||||
|
||||
return util.ConvertMap[*issues_model.PullRequest, *PullRequest](pullRequests, PullRequestConverter)
|
||||
}
|
||||
|
||||
func (o *PullRequestProvider) ProcessObject(user *User, project *Project, pr *PullRequest) {
|
||||
if err := pr.LoadIssue(o.g.ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := pr.Issue.LoadRepo(o.g.ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := pr.LoadAttributes(o.g.ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := pr.LoadBaseRepo(o.g.ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := pr.LoadHeadRepo(o.g.ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pr.FetchFunc = func(repository string) string {
|
||||
head, messages := f3_gitea.UpdateGitForPullRequest(o.g.ctx, &o.prHeadCache, pr.ToFormat(), repository)
|
||||
for _, message := range messages {
|
||||
o.g.GetLogger().Warn(message)
|
||||
}
|
||||
o.cleanupRemotes(repository)
|
||||
return head
|
||||
}
|
||||
}
|
||||
|
||||
func (o *PullRequestProvider) Get(user *User, project *Project, pullRequest *PullRequest) *PullRequest {
|
||||
id := pullRequest.GetID()
|
||||
pr, err := issues_model.GetPullRequestByIndex(o.g.ctx, project.GetID(), id)
|
||||
if issues_model.IsErrPullRequestNotExist(err) {
|
||||
return &PullRequest{}
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p := PullRequestConverter(pr)
|
||||
o.ProcessObject(user, project, p)
|
||||
return p
|
||||
}
|
||||
|
||||
func (o *PullRequestProvider) Put(user *User, project *Project, pullRequest *PullRequest) *PullRequest {
|
||||
i := pullRequest.PullRequest.Issue
|
||||
i.RepoID = project.GetID()
|
||||
labels := make([]int64, 0, len(i.Labels))
|
||||
for _, label := range i.Labels {
|
||||
labels = append(labels, label.ID)
|
||||
}
|
||||
|
||||
if err := issues_model.NewPullRequest(o.g.ctx, &project.Repository, i, labels, []string{}, &pullRequest.PullRequest); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o.Get(user, project, pullRequest)
|
||||
}
|
||||
|
||||
func (o *PullRequestProvider) Delete(user *User, project *Project, pullRequest *PullRequest) *PullRequest {
|
||||
p := o.Get(user, project, pullRequest)
|
||||
if !p.IsNil() {
|
||||
repoPath := repo_model.RepoPath(user.Name, project.Name)
|
||||
gitRepo, err := git.OpenRepository(o.g.ctx, repoPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
if err := issue_service.DeleteIssue(o.g.GetDoer(), gitRepo, p.PullRequest.Issue); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type Reaction struct {
|
||||
issues_model.Reaction
|
||||
}
|
||||
|
||||
func ReactionConverter(f *issues_model.Reaction) *Reaction {
|
||||
return &Reaction{
|
||||
Reaction: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Reaction) GetID() int64 {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
func (o *Reaction) SetID(id int64) {
|
||||
o.ID = id
|
||||
}
|
||||
|
||||
func (o *Reaction) IsNil() bool {
|
||||
return o.ID == 0
|
||||
}
|
||||
|
||||
func (o *Reaction) Equals(other *Reaction) bool {
|
||||
return o.UserID == other.UserID && o.Type == other.Type
|
||||
}
|
||||
|
||||
func (o *Reaction) ToFormat() *format.Reaction {
|
||||
return &format.Reaction{
|
||||
Common: format.Common{Index: o.ID},
|
||||
UserID: o.User.ID,
|
||||
Content: o.Type,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Reaction) FromFormat(reaction *format.Reaction) {
|
||||
*o = Reaction{
|
||||
Reaction: issues_model.Reaction{
|
||||
ID: reaction.GetID(),
|
||||
UserID: reaction.UserID,
|
||||
User: &user_model.User{
|
||||
ID: reaction.UserID,
|
||||
},
|
||||
Type: reaction.Content,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type ReactionProvider struct {
|
||||
g *Gitea
|
||||
}
|
||||
|
||||
func (o *ReactionProvider) GetReferences(reaction *Reaction) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ReactionProvider) ToFormat(reaction *Reaction) *format.Reaction {
|
||||
return reaction.ToFormat()
|
||||
}
|
||||
|
||||
func (o *ReactionProvider) FromFormat(m *format.Reaction) *Reaction {
|
||||
var reaction Reaction
|
||||
reaction.FromFormat(m)
|
||||
return &reaction
|
||||
}
|
||||
|
||||
//
|
||||
// Although it would be possible to use a higher level logic instead of the database,
|
||||
// as of September 2022 (1.18 dev)
|
||||
// (i) models/issues/reaction.go imposes a significant overhead
|
||||
// (ii) is fragile and bugous https://github.com/go-gitea/gitea/issues/20860
|
||||
//
|
||||
|
||||
func (o *ReactionProvider) GetObjects(user *User, project *Project, parents []common.ContainerObjectInterface, page int) []*Reaction {
|
||||
cond := builder.NewCond()
|
||||
switch l := parents[len(parents)-1].(type) {
|
||||
case *Issue:
|
||||
cond = cond.And(builder.Eq{"reaction.issue_id": l.ID})
|
||||
cond = cond.And(builder.Eq{"reaction.comment_id": 0})
|
||||
case *Comment:
|
||||
cond = cond.And(builder.Eq{"reaction.comment_id": l.ID})
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected type %T", parents[len(parents)-1]))
|
||||
}
|
||||
sess := db.GetEngine(o.g.ctx).Where(cond)
|
||||
if page > 0 {
|
||||
sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: o.g.perPage})
|
||||
}
|
||||
reactions := make([]*issues_model.Reaction, 0, 10)
|
||||
if err := sess.Find(&reactions); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err := (issues_model.ReactionList)(reactions).LoadUsers(o.g.ctx, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return util.ConvertMap[*issues_model.Reaction, *Reaction](reactions, ReactionConverter)
|
||||
}
|
||||
|
||||
func (o *ReactionProvider) ProcessObject(user *User, project *Project, parents []common.ContainerObjectInterface, reaction *Reaction) {
|
||||
}
|
||||
|
||||
func (o *ReactionProvider) Get(user *User, project *Project, parents []common.ContainerObjectInterface, exemplar *Reaction) *Reaction {
|
||||
reaction := &Reaction{}
|
||||
has, err := db.GetEngine(o.g.ctx).ID(exemplar.GetID()).Get(&reaction.Reaction)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else if !has {
|
||||
return &Reaction{}
|
||||
}
|
||||
if _, err := (issues_model.ReactionList{&reaction.Reaction}).LoadUsers(o.g.ctx, nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return reaction
|
||||
}
|
||||
|
||||
func (o *ReactionProvider) Put(user *User, project *Project, parents []common.ContainerObjectInterface, reaction *Reaction) *Reaction {
|
||||
r := &issues_model.Reaction{
|
||||
Type: reaction.Type,
|
||||
UserID: o.g.GetDoer().ID,
|
||||
}
|
||||
switch l := parents[len(parents)-1].(type) {
|
||||
case *Issue:
|
||||
r.IssueID = l.ID
|
||||
r.CommentID = 0
|
||||
case *Comment:
|
||||
i, ok := parents[len(parents)-2].(*Issue)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("unexpected type %T", parents[len(parents)-2]))
|
||||
}
|
||||
r.IssueID = i.ID
|
||||
r.CommentID = l.ID
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected type %T", parents[len(parents)-1]))
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(o.g.ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if _, err := db.GetEngine(ctx).Insert(r); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := committer.Commit(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ReactionConverter(r)
|
||||
}
|
||||
|
||||
func (o *ReactionProvider) Delete(user *User, project *Project, parents []common.ContainerObjectInterface, reaction *Reaction) *Reaction {
|
||||
r := o.Get(user, project, parents, reaction)
|
||||
if !r.IsNil() {
|
||||
if _, err := db.GetEngine(o.g.ctx).Delete(&reaction.Reaction); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return reaction
|
||||
}
|
||||
return r
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
repo_model.Release
|
||||
}
|
||||
|
||||
func ReleaseConverter(f *repo_model.Release) *Release {
|
||||
return &Release{
|
||||
Release: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Release) GetID() int64 {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
func (o *Release) SetID(id int64) {
|
||||
o.ID = id
|
||||
}
|
||||
|
||||
func (o *Release) IsNil() bool {
|
||||
return o.ID == 0
|
||||
}
|
||||
|
||||
func (o *Release) Equals(other *Release) bool {
|
||||
return o.ID == other.ID
|
||||
}
|
||||
|
||||
func (o *Release) ToFormat() *format.Release {
|
||||
return &format.Release{
|
||||
Common: format.Common{Index: o.ID},
|
||||
TagName: o.TagName,
|
||||
TargetCommitish: o.Target,
|
||||
Name: o.Title,
|
||||
Body: o.Note,
|
||||
Draft: o.IsDraft,
|
||||
Prerelease: o.IsPrerelease,
|
||||
Created: o.CreatedUnix.AsTime(),
|
||||
PublisherID: o.Publisher.ID,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Release) FromFormat(release *format.Release) {
|
||||
if release.Created.IsZero() {
|
||||
if !release.Published.IsZero() {
|
||||
release.Created = release.Published
|
||||
} else {
|
||||
release.Created = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
*o = Release{
|
||||
repo_model.Release{
|
||||
PublisherID: release.PublisherID,
|
||||
Publisher: &user_model.User{
|
||||
ID: release.PublisherID,
|
||||
},
|
||||
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()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type ReleaseProvider struct {
|
||||
g *Gitea
|
||||
}
|
||||
|
||||
func (o *ReleaseProvider) GetReferences(release *Release) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ReleaseProvider) ToFormat(release *Release) *format.Release {
|
||||
return release.ToFormat()
|
||||
}
|
||||
|
||||
func (o *ReleaseProvider) FromFormat(i *format.Release) *Release {
|
||||
var release Release
|
||||
release.FromFormat(i)
|
||||
return &release
|
||||
}
|
||||
|
||||
func (o *ReleaseProvider) GetObjects(user *User, project *Project, page int) []*Release {
|
||||
releases, err := repo_model.GetReleasesByRepoID(o.g.ctx, project.GetID(), repo_model.FindReleasesOptions{
|
||||
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
|
||||
IncludeDrafts: true,
|
||||
IncludeTags: false,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error while listing releases: %v", err))
|
||||
}
|
||||
|
||||
return util.ConvertMap[*repo_model.Release, *Release](releases, ReleaseConverter)
|
||||
}
|
||||
|
||||
func (o *ReleaseProvider) ProcessObject(user *User, project *Project, release *Release) {
|
||||
if err := (&release.Release).LoadAttributes(o.g.ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ReleaseProvider) Get(user *User, project *Project, exemplar *Release) *Release {
|
||||
id := exemplar.GetID()
|
||||
release, err := repo_model.GetReleaseByID(o.g.ctx, id)
|
||||
if repo_model.IsErrReleaseNotExist(err) {
|
||||
return &Release{}
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
r := ReleaseConverter(release)
|
||||
o.ProcessObject(user, project, r)
|
||||
return r
|
||||
}
|
||||
|
||||
func (o *ReleaseProvider) Put(user *User, project *Project, release *Release) *Release {
|
||||
r := release.Release
|
||||
r.RepoID = project.GetID()
|
||||
|
||||
repoPath := repo_model.RepoPath(user.Name, project.Name)
|
||||
gitRepo, err := git.OpenRepository(o.g.ctx, repoPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
if err := release_service.CreateRelease(gitRepo, &r, nil, ""); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o.Get(user, project, ReleaseConverter(&r))
|
||||
}
|
||||
|
||||
func (o *ReleaseProvider) Delete(user *User, project *Project, release *Release) *Release {
|
||||
m := o.Get(user, project, release)
|
||||
if !m.IsNil() {
|
||||
if err := release_service.DeleteReleaseByID(o.g.ctx, release.GetID(), o.g.GetDoer(), false); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
base "code.gitea.io/gitea/modules/migration"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
format.Repository
|
||||
}
|
||||
|
||||
func (o *Repository) Equals(other *Repository) bool {
|
||||
return false // it is costly to figure that out, mirroring is as fast
|
||||
}
|
||||
|
||||
func (o *Repository) ToFormat() *format.Repository {
|
||||
return &o.Repository
|
||||
}
|
||||
|
||||
func (o *Repository) FromFormat(repository *format.Repository) {
|
||||
o.Repository = *repository
|
||||
}
|
||||
|
||||
type RepositoryProvider struct {
|
||||
g *Gitea
|
||||
}
|
||||
|
||||
func (o *RepositoryProvider) GetReferences(repository *Repository) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *RepositoryProvider) ToFormat(repository *Repository) *format.Repository {
|
||||
return repository.ToFormat()
|
||||
}
|
||||
|
||||
func (o *RepositoryProvider) FromFormat(p *format.Repository) *Repository {
|
||||
var repository Repository
|
||||
repository.FromFormat(p)
|
||||
return &repository
|
||||
}
|
||||
|
||||
func (o *RepositoryProvider) GetObjects(user *User, project *Project, page int) []*Repository {
|
||||
if page > 0 {
|
||||
return make([]*Repository, 0)
|
||||
}
|
||||
repositories := make([]*Repository, len(format.RepositoryNames))
|
||||
for _, name := range format.RepositoryNames {
|
||||
repositories = append(repositories, o.Get(user, project, &Repository{
|
||||
Repository: format.Repository{
|
||||
Name: name,
|
||||
},
|
||||
}))
|
||||
}
|
||||
return repositories
|
||||
}
|
||||
|
||||
func (o *RepositoryProvider) ProcessObject(user *User, project *Project, repository *Repository) {
|
||||
}
|
||||
|
||||
func (o *RepositoryProvider) Get(user *User, project *Project, exemplar *Repository) *Repository {
|
||||
repoPath := repo_model.RepoPath(user.Name, project.Name) + exemplar.Name
|
||||
return &Repository{
|
||||
Repository: format.Repository{
|
||||
Name: exemplar.Name,
|
||||
FetchFunc: func(destination string) {
|
||||
util.Command(o.g.ctx, "git", "clone", "--bare", repoPath, destination)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *RepositoryProvider) Put(user *User, project *Project, repository *Repository) *Repository {
|
||||
if repository.FetchFunc != nil {
|
||||
directory, delete := format.RepositoryDefaultDirectory()
|
||||
defer delete()
|
||||
repository.FetchFunc(directory)
|
||||
|
||||
_, err := repo_module.MigrateRepositoryGitData(o.g.ctx, &user.User, &project.Repository, base.MigrateOptions{
|
||||
RepoName: project.Name,
|
||||
Mirror: false,
|
||||
MirrorInterval: "",
|
||||
LFS: false,
|
||||
LFSEndpoint: "",
|
||||
CloneAddr: directory,
|
||||
Wiki: o.g.GetOptions().GetFeatures().Wiki,
|
||||
Releases: o.g.GetOptions().GetFeatures().Releases,
|
||||
}, migrations.NewMigrationHTTPTransport())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return o.Get(user, project, repository)
|
||||
}
|
||||
|
||||
func (o *RepositoryProvider) Delete(user *User, project *Project, repository *Repository) *Repository {
|
||||
panic("It is not possible to delete a repository")
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"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/convert"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type Review struct {
|
||||
issues_model.Review
|
||||
}
|
||||
|
||||
func ReviewConverter(f *issues_model.Review) *Review {
|
||||
return &Review{
|
||||
Review: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Review) GetID() int64 {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
func (o *Review) SetID(id int64) {
|
||||
o.ID = id
|
||||
}
|
||||
|
||||
func (o *Review) IsNil() bool {
|
||||
return o.ID == 0
|
||||
}
|
||||
|
||||
func (o *Review) Equals(other *Review) bool {
|
||||
return o.Content == other.Content
|
||||
}
|
||||
|
||||
func (o *Review) ToFormat() *format.Review {
|
||||
comments := make([]*format.ReviewComment, 0, len(o.Comments))
|
||||
for _, comment := range o.Comments {
|
||||
comments = append(comments, &format.ReviewComment{
|
||||
Common: format.Common{Index: comment.ID},
|
||||
// InReplyTo
|
||||
Content: comment.Content,
|
||||
TreePath: comment.TreePath,
|
||||
DiffHunk: convert.Patch2diff(comment.Patch),
|
||||
Patch: comment.Patch,
|
||||
Line: int(comment.Line),
|
||||
CommitID: comment.CommitSHA,
|
||||
PosterID: comment.PosterID,
|
||||
CreatedAt: comment.CreatedUnix.AsTime(),
|
||||
UpdatedAt: comment.UpdatedUnix.AsTime(),
|
||||
})
|
||||
}
|
||||
|
||||
review := format.Review{
|
||||
Common: format.Common{Index: o.Review.ID},
|
||||
IssueIndex: o.IssueID,
|
||||
Official: o.Review.Official,
|
||||
CommitID: o.Review.CommitID,
|
||||
Content: o.Review.Content,
|
||||
CreatedAt: o.Review.CreatedUnix.AsTime(),
|
||||
State: format.ReviewStateUnknown,
|
||||
Comments: comments,
|
||||
}
|
||||
|
||||
if o.Review.Reviewer != nil {
|
||||
review.ReviewerID = o.Review.Reviewer.ID
|
||||
}
|
||||
|
||||
switch o.Type {
|
||||
case issues_model.ReviewTypeApprove:
|
||||
review.State = format.ReviewStateApproved
|
||||
case issues_model.ReviewTypeReject:
|
||||
review.State = format.ReviewStateChangesRequested
|
||||
case issues_model.ReviewTypeComment:
|
||||
review.State = format.ReviewStateCommented
|
||||
case issues_model.ReviewTypePending:
|
||||
review.State = format.ReviewStatePending
|
||||
case issues_model.ReviewTypeRequest:
|
||||
review.State = format.ReviewStateRequestReview
|
||||
}
|
||||
|
||||
return &review
|
||||
}
|
||||
|
||||
func (o *Review) FromFormat(review *format.Review) {
|
||||
comments := make([]*issues_model.Comment, 0, len(review.Comments))
|
||||
for _, comment := range review.Comments {
|
||||
comments = append(comments, &issues_model.Comment{
|
||||
ID: comment.GetID(),
|
||||
Type: issues_model.CommentTypeReview,
|
||||
// InReplyTo
|
||||
CommitSHA: comment.CommitID,
|
||||
Line: int64(comment.Line),
|
||||
TreePath: comment.TreePath,
|
||||
Content: comment.Content,
|
||||
Patch: comment.Patch,
|
||||
PosterID: comment.PosterID,
|
||||
CreatedUnix: timeutil.TimeStamp(comment.CreatedAt.Unix()),
|
||||
UpdatedUnix: timeutil.TimeStamp(comment.UpdatedAt.Unix()),
|
||||
})
|
||||
}
|
||||
*o = Review{
|
||||
Review: issues_model.Review{
|
||||
ID: review.GetID(),
|
||||
Reviewer: &user_model.User{
|
||||
ID: review.ReviewerID,
|
||||
},
|
||||
IssueID: review.IssueIndex,
|
||||
Official: review.Official,
|
||||
CommitID: review.CommitID,
|
||||
Content: review.Content,
|
||||
CreatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
|
||||
Comments: comments,
|
||||
},
|
||||
}
|
||||
|
||||
switch review.State {
|
||||
case format.ReviewStateApproved:
|
||||
o.Type = issues_model.ReviewTypeApprove
|
||||
case format.ReviewStateChangesRequested:
|
||||
o.Type = issues_model.ReviewTypeReject
|
||||
case format.ReviewStateCommented:
|
||||
o.Type = issues_model.ReviewTypeComment
|
||||
case format.ReviewStatePending:
|
||||
o.Type = issues_model.ReviewTypePending
|
||||
case format.ReviewStateRequestReview:
|
||||
o.Type = issues_model.ReviewTypeRequest
|
||||
}
|
||||
}
|
||||
|
||||
type ReviewProvider struct {
|
||||
g *Gitea
|
||||
}
|
||||
|
||||
func (o *ReviewProvider) GetReferences(review *Review) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ReviewProvider) ToFormat(review *Review) *format.Review {
|
||||
return review.ToFormat()
|
||||
}
|
||||
|
||||
func (o *ReviewProvider) FromFormat(i *format.Review) *Review {
|
||||
var review Review
|
||||
review.FromFormat(i)
|
||||
return &review
|
||||
}
|
||||
|
||||
func (o *ReviewProvider) GetObjects(user *User, project *Project, pullRequest *PullRequest, page int) []*Review {
|
||||
reviews, err := issues_model.FindReviews(o.g.ctx, issues_model.FindReviewOptions{
|
||||
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
|
||||
IssueID: pullRequest.IssueID,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error while listing reviews: %v", err))
|
||||
}
|
||||
|
||||
return util.ConvertMap[*issues_model.Review, *Review](reviews, ReviewConverter)
|
||||
}
|
||||
|
||||
func (o *ReviewProvider) ProcessObject(user *User, project *Project, pullRequest *PullRequest, review *Review) {
|
||||
if err := (&review.Review).LoadAttributes(o.g.ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ReviewProvider) Get(user *User, project *Project, pullRequest *PullRequest, exemplar *Review) *Review {
|
||||
id := exemplar.GetID()
|
||||
review, err := issues_model.GetReviewByID(o.g.ctx, id)
|
||||
if issues_model.IsErrReviewNotExist(err) {
|
||||
return &Review{}
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := review.LoadAttributes(o.g.ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ReviewConverter(review)
|
||||
}
|
||||
|
||||
func (o *ReviewProvider) Put(user *User, project *Project, pullRequest *PullRequest, review *Review) *Review {
|
||||
r := &review.Review
|
||||
r.ID = 0
|
||||
for _, comment := range r.Comments {
|
||||
comment.ID = 0
|
||||
}
|
||||
r.IssueID = pullRequest.IssueID
|
||||
// this is a workaround because the id remapping logic is missing, wait until it is implemented and remove it
|
||||
u, err := user_model.GetUserByName(o.g.ctx, r.Reviewer.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
r.ReviewerID = u.ID
|
||||
if err := issues_model.InsertReviews([]*issues_model.Review{r}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o.Get(user, project, pullRequest, ReviewConverter(r))
|
||||
}
|
||||
|
||||
func (o *ReviewProvider) Delete(user *User, project *Project, pullRequest *PullRequest, review *Review) *Review {
|
||||
r := o.Get(user, project, pullRequest, review)
|
||||
if !r.IsNil() {
|
||||
if err := issues_model.DeleteReview(&r.Review); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type Topic struct {
|
||||
repo_model.Topic
|
||||
}
|
||||
|
||||
func TopicConverter(f *repo_model.Topic) *Topic {
|
||||
return &Topic{
|
||||
Topic: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Topic) GetID() int64 {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
func (o *Topic) SetID(id int64) {
|
||||
o.ID = id
|
||||
}
|
||||
|
||||
func (o *Topic) IsNil() bool {
|
||||
return o.ID == 0
|
||||
}
|
||||
|
||||
func (o *Topic) Equals(other *Topic) bool {
|
||||
return o.Name == other.Name
|
||||
}
|
||||
|
||||
func (o *Topic) ToFormat() *format.Topic {
|
||||
return &format.Topic{
|
||||
Common: format.Common{Index: o.ID},
|
||||
Name: o.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Topic) FromFormat(topic *format.Topic) {
|
||||
*o = Topic{
|
||||
Topic: repo_model.Topic{
|
||||
ID: topic.Index,
|
||||
Name: topic.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type TopicProvider struct {
|
||||
g *Gitea
|
||||
}
|
||||
|
||||
func (o *TopicProvider) GetReferences(topic *Topic) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *TopicProvider) ToFormat(topic *Topic) *format.Topic {
|
||||
return topic.ToFormat()
|
||||
}
|
||||
|
||||
func (o *TopicProvider) FromFormat(m *format.Topic) *Topic {
|
||||
var topic Topic
|
||||
topic.FromFormat(m)
|
||||
return &topic
|
||||
}
|
||||
|
||||
func (o *TopicProvider) GetObjects(user *User, project *Project, page int) []*Topic {
|
||||
topics, _, err := repo_model.FindTopics(&repo_model.FindTopicOptions{
|
||||
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
|
||||
RepoID: project.GetID(),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return util.ConvertMap[*repo_model.Topic, *Topic](topics, TopicConverter)
|
||||
}
|
||||
|
||||
func (o *TopicProvider) ProcessObject(user *User, project *Project, topic *Topic) {
|
||||
}
|
||||
|
||||
func (o *TopicProvider) Get(user *User, project *Project, exemplar *Topic) *Topic {
|
||||
id := exemplar.GetID()
|
||||
topic, err := repo_model.GetRepoTopicByID(o.g.ctx, project.GetID(), id)
|
||||
if repo_model.IsErrTopicNotExist(err) {
|
||||
return &Topic{}
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return TopicConverter(topic)
|
||||
}
|
||||
|
||||
func (o *TopicProvider) Put(user *User, project *Project, topic *Topic) *Topic {
|
||||
t, err := repo_model.AddTopic(project.GetID(), topic.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o.Get(user, project, TopicConverter(t))
|
||||
}
|
||||
|
||||
func (o *TopicProvider) Delete(user *User, project *Project, topic *Topic) *Topic {
|
||||
t := o.Get(user, project, topic)
|
||||
if !t.IsNil() {
|
||||
t, err := repo_model.DeleteTopic(project.GetID(), t.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return TopicConverter(t)
|
||||
}
|
||||
return t
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
f3_util "lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
user_model.User
|
||||
}
|
||||
|
||||
func UserConverter(f *user_model.User) *User {
|
||||
return &User{
|
||||
User: *f,
|
||||
}
|
||||
}
|
||||
|
||||
func (o User) GetID() int64 {
|
||||
return o.ID
|
||||
}
|
||||
|
||||
func (o *User) SetID(id int64) {
|
||||
o.ID = id
|
||||
}
|
||||
|
||||
func (o *User) IsNil() bool {
|
||||
return o.ID == 0
|
||||
}
|
||||
|
||||
func (o *User) Equals(other *User) bool {
|
||||
return (o.Name == other.Name)
|
||||
}
|
||||
|
||||
func (o *User) ToFormat() *format.User {
|
||||
return &format.User{
|
||||
Common: format.Common{Index: o.ID},
|
||||
UserName: o.Name,
|
||||
Name: o.FullName,
|
||||
Email: o.Email,
|
||||
Password: o.Passwd,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *User) FromFormat(user *format.User) {
|
||||
*o = User{
|
||||
User: user_model.User{
|
||||
ID: user.Index,
|
||||
Name: user.UserName,
|
||||
FullName: user.Name,
|
||||
Email: user.Email,
|
||||
Passwd: user.Password,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type UserProvider struct {
|
||||
g *Gitea
|
||||
}
|
||||
|
||||
func (o *UserProvider) GetReferences(user *User) []common.Reference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *UserProvider) ToFormat(user *User) *format.User {
|
||||
return user.ToFormat()
|
||||
}
|
||||
|
||||
func (o *UserProvider) FromFormat(p *format.User) *User {
|
||||
var user User
|
||||
user.FromFormat(p)
|
||||
return &user
|
||||
}
|
||||
|
||||
func (o *UserProvider) GetObjects(page int) []*User {
|
||||
users, _, err := user_model.SearchUsers(&user_model.SearchUserOptions{
|
||||
Actor: o.g.GetDoer(),
|
||||
Type: user_model.UserTypeIndividual,
|
||||
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error while listing users: %v", err))
|
||||
}
|
||||
return f3_util.ConvertMap[*user_model.User, *User](users, UserConverter)
|
||||
}
|
||||
|
||||
func (o *UserProvider) ProcessObject(user *User) {
|
||||
}
|
||||
|
||||
func (o *UserProvider) Get(exemplar *User) *User {
|
||||
var user *user_model.User
|
||||
var err error
|
||||
if exemplar.GetID() > 0 {
|
||||
user, err = user_model.GetUserByIDCtx(o.g.ctx, exemplar.GetID())
|
||||
} else if exemplar.Name != "" {
|
||||
user, err = user_model.GetUserByName(o.g.ctx, exemplar.Name)
|
||||
} else {
|
||||
panic("GetID() == 0 and UserName == \"\"")
|
||||
}
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
return &User{}
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("user %v %w", exemplar, err))
|
||||
}
|
||||
return UserConverter(user)
|
||||
}
|
||||
|
||||
func (o *UserProvider) Put(user *User) *User {
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
}
|
||||
u := user.User
|
||||
err := user_model.CreateUser(&u, overwriteDefault)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return o.Get(UserConverter(&u))
|
||||
}
|
||||
|
||||
func (o *UserProvider) Delete(user *User) *User {
|
||||
u := o.Get(user)
|
||||
if !u.IsNil() {
|
||||
if err := user_service.DeleteUser(o.g.ctx, &user.User, true); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return u
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
base "code.gitea.io/gitea/modules/migration"
|
||||
"code.gitea.io/gitea/services/f3/driver"
|
||||
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3"
|
||||
f3_forges "lab.forgefriends.org/friendlyforgeformat/gof3/forges"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/f3"
|
||||
)
|
||||
|
||||
func ToF3Logger(messenger base.Messenger) gof3.Logger {
|
||||
if messenger == nil {
|
||||
messenger = func(string, ...interface{}) {}
|
||||
}
|
||||
return gof3.Logger{
|
||||
Message: messenger,
|
||||
Trace: log.Trace,
|
||||
Debug: log.Debug,
|
||||
Info: log.Info,
|
||||
Warn: log.Warn,
|
||||
Error: log.Error,
|
||||
Critical: log.Critical,
|
||||
Fatal: log.Fatal,
|
||||
}
|
||||
}
|
||||
|
||||
func GiteaForgeRoot(ctx context.Context, features gof3.Features, doer *user_model.User) *f3_forges.ForgeRoot {
|
||||
forgeRoot := f3_forges.NewForgeRootFromDriver(&driver.Gitea{}, &driver.Options{
|
||||
Options: gof3.Options{
|
||||
Features: features,
|
||||
Logger: ToF3Logger(nil),
|
||||
},
|
||||
Doer: doer,
|
||||
})
|
||||
forgeRoot.SetContext(ctx)
|
||||
return forgeRoot
|
||||
}
|
||||
|
||||
func F3ForgeRoot(ctx context.Context, features gof3.Features, directory string) *f3_forges.ForgeRoot {
|
||||
forgeRoot := f3_forges.NewForgeRoot(&f3.Options{
|
||||
Options: gof3.Options{
|
||||
Configuration: gof3.Configuration{
|
||||
Directory: directory,
|
||||
},
|
||||
Features: features,
|
||||
Logger: ToF3Logger(nil),
|
||||
},
|
||||
Remap: true,
|
||||
})
|
||||
forgeRoot.SetContext(ctx)
|
||||
return forgeRoot
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/cmd"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
f3_forges "lab.forgefriends.org/friendlyforgeformat/gof3/forges"
|
||||
f3_util "lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
func Test_CmdF3(t *testing.T) {
|
||||
onGiteaRun(t, func(*testing.T, *url.URL) {
|
||||
AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
|
||||
setting.Migrations.AllowLocalNetworks = true
|
||||
// without migrations.Init() AllowLocalNetworks = true is not effective and
|
||||
// a http call fails with "...migration can only call allowed HTTP servers..."
|
||||
migrations.Init()
|
||||
AppVer := setting.AppVer
|
||||
// Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string.
|
||||
setting.AppVer = "1.16.0"
|
||||
defer func() {
|
||||
setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
|
||||
setting.AppVer = AppVer
|
||||
}()
|
||||
|
||||
//
|
||||
// Step 1: create a fixture
|
||||
//
|
||||
fixture := f3_forges.NewFixture(t, f3_forges.FixtureF3Factory)
|
||||
fixture.NewUser()
|
||||
fixture.NewMilestone()
|
||||
fixture.NewLabel()
|
||||
fixture.NewIssue()
|
||||
fixture.NewTopic()
|
||||
fixture.NewRepository()
|
||||
fixture.NewRelease()
|
||||
fixture.NewAsset()
|
||||
fixture.NewIssueComment()
|
||||
fixture.NewIssueReaction()
|
||||
|
||||
//
|
||||
// Step 2: import the fixture into Gitea
|
||||
//
|
||||
cmd.CmdF3.Action = func(ctx *cli.Context) { cmd.RunF3(context.Background(), ctx) }
|
||||
{
|
||||
realStdout := os.Stdout // Backup Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
set := flag.NewFlagSet("f3", 0)
|
||||
_ = set.Parse([]string{"f3", "--import", "--directory", fixture.ForgeRoot.GetDirectory()})
|
||||
cliContext := cli.NewContext(&cli.App{Writer: os.Stdout}, set, nil)
|
||||
assert.NoError(t, cmd.CmdF3.Run(cliContext))
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
commandOutput := buf.String()
|
||||
assert.EqualValues(t, "imported\n", commandOutput)
|
||||
os.Stdout = realStdout
|
||||
}
|
||||
|
||||
//
|
||||
// Step 3: export Gitea into F3
|
||||
//
|
||||
directory := t.TempDir()
|
||||
{
|
||||
realStdout := os.Stdout // Backup Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
set := flag.NewFlagSet("f3", 0)
|
||||
_ = set.Parse([]string{"f3", "--export", "--no-pull-request", "--user", fixture.UserFormat.UserName, "--repository", fixture.ProjectFormat.Name, "--directory", directory})
|
||||
cliContext := cli.NewContext(&cli.App{Writer: os.Stdout}, set, nil)
|
||||
assert.NoError(t, cmd.CmdF3.Run(cliContext))
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
commandOutput := buf.String()
|
||||
assert.EqualValues(t, "exported\n", commandOutput)
|
||||
os.Stdout = realStdout
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// Step 4: verify the export and import are equivalent
|
||||
//
|
||||
files := f3_util.Command(context.Background(), "find", directory)
|
||||
assert.Contains(t, files, "/label/")
|
||||
assert.Contains(t, files, "/issue/")
|
||||
assert.Contains(t, files, "/milestone/")
|
||||
assert.Contains(t, files, "/topic/")
|
||||
assert.Contains(t, files, "/release/")
|
||||
assert.Contains(t, files, "/asset/")
|
||||
assert.Contains(t, files, "/reaction/")
|
||||
})
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/f3/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3"
|
||||
f3_forges "lab.forgefriends.org/friendlyforgeformat/gof3/forges"
|
||||
f3_f3 "lab.forgefriends.org/friendlyforgeformat/gof3/forges/f3"
|
||||
f3_gitea "lab.forgefriends.org/friendlyforgeformat/gof3/forges/gitea"
|
||||
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
|
||||
f3_util "lab.forgefriends.org/friendlyforgeformat/gof3/util"
|
||||
)
|
||||
|
||||
func TestF3(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
|
||||
setting.Migrations.AllowLocalNetworks = true
|
||||
AppVer := setting.AppVer
|
||||
// Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string.
|
||||
setting.AppVer = "1.16.0"
|
||||
defer func() {
|
||||
setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
|
||||
setting.AppVer = AppVer
|
||||
}()
|
||||
|
||||
//
|
||||
// Step 1: create a fixture
|
||||
//
|
||||
fixtureNewF3Forge := func(t *testing.T, user *format.User) *f3_forges.ForgeRoot {
|
||||
root := f3_forges.NewForgeRoot(&f3_f3.Options{
|
||||
Options: gof3.Options{
|
||||
Configuration: gof3.Configuration{
|
||||
Directory: t.TempDir(),
|
||||
},
|
||||
Features: gof3.AllFeatures,
|
||||
},
|
||||
Remap: true,
|
||||
})
|
||||
root.SetContext(context.Background())
|
||||
return root
|
||||
}
|
||||
fixture := f3_forges.NewFixture(t, f3_forges.FixtureForgeFactory{Fun: fixtureNewF3Forge, RootRequired: false})
|
||||
fixture.NewUser()
|
||||
fixture.NewMilestone()
|
||||
fixture.NewLabel()
|
||||
fixture.NewIssue()
|
||||
fixture.NewTopic()
|
||||
fixture.NewRepository()
|
||||
fixture.NewPullRequest()
|
||||
fixture.NewRelease()
|
||||
fixture.NewAsset()
|
||||
fixture.NewIssueComment()
|
||||
fixture.NewPullRequestComment()
|
||||
fixture.NewReview()
|
||||
fixture.NewIssueReaction()
|
||||
fixture.NewCommentReaction()
|
||||
|
||||
//
|
||||
// Step 2: mirror the fixture into Gitea
|
||||
//
|
||||
doer, err := user_model.GetAdminUser()
|
||||
assert.NoError(t, err)
|
||||
|
||||
giteaLocal := util.GiteaForgeRoot(context.Background(), gof3.AllFeatures, doer)
|
||||
giteaLocal.Forge.Mirror(fixture.Forge)
|
||||
|
||||
//
|
||||
// Step 3: mirror Gitea into F3
|
||||
//
|
||||
adminUsername := "user1"
|
||||
giteaAPI := f3_forges.NewForgeRootFromDriver(&f3_gitea.Gitea{}, &f3_gitea.Options{
|
||||
Options: gof3.Options{
|
||||
Configuration: gof3.Configuration{
|
||||
URL: setting.AppURL,
|
||||
Directory: t.TempDir(),
|
||||
},
|
||||
Features: gof3.AllFeatures,
|
||||
},
|
||||
AuthToken: getUserToken(t, adminUsername),
|
||||
})
|
||||
giteaAPI.SetContext(context.Background())
|
||||
|
||||
f3 := f3_forges.FixtureNewF3Forge(t, nil)
|
||||
apiForge := giteaAPI.Forge
|
||||
apiUser := apiForge.Users.GetFromFormat(&format.User{UserName: fixture.UserFormat.UserName})
|
||||
apiProject := apiUser.Projects.GetFromFormat(&format.Project{Name: fixture.ProjectFormat.Name})
|
||||
f3.Forge.Mirror(apiForge, apiUser, apiProject)
|
||||
|
||||
//
|
||||
// Step 4: verify the fixture and F3 are equivalent
|
||||
//
|
||||
files := f3_util.Command(context.Background(), "find", f3.GetDirectory())
|
||||
assert.Contains(t, files, "/repository/git/hooks")
|
||||
assert.Contains(t, files, "/label/")
|
||||
assert.Contains(t, files, "/issue/")
|
||||
assert.Contains(t, files, "/milestone/")
|
||||
assert.Contains(t, files, "/topic/")
|
||||
assert.Contains(t, files, "/pull_request/")
|
||||
assert.Contains(t, files, "/release/")
|
||||
assert.Contains(t, files, "/asset/")
|
||||
assert.Contains(t, files, "/comment/")
|
||||
assert.Contains(t, files, "/review/")
|
||||
assert.Contains(t, files, "/reaction/")
|
||||
// f3_util.Command(context.Background(), "cp", "-a", f3.GetDirectory(), "abc")
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue