diff --git a/.drone.yml b/.drone.yml
index c72c4226d..086c19627 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -554,7 +554,7 @@ steps:
# TODO: We should probably build all dependencies into a test image
- name: test-e2e
- image: mcr.microsoft.com/playwright:v1.23.1-focal
+ image: mcr.microsoft.com/playwright:v1.24.0-focal
commands:
- curl -sLO https://go.dev/dl/go1.18.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.18.linux-amd64.tar.gz
- groupadd --gid 1001 gitea && useradd -m --gid 1001 --uid 1001 gitea
diff --git a/go.mod b/go.mod
index 0e93ea443..e684cca13 100644
--- a/go.mod
+++ b/go.mod
@@ -54,7 +54,7 @@ require (
github.com/google/uuid v1.3.0
github.com/gorilla/feeds v1.1.1
github.com/gorilla/sessions v1.2.1
- github.com/hashicorp/go-version v1.4.0
+ github.com/hashicorp/go-version v1.5.0
github.com/hashicorp/golang-lru v0.5.4
github.com/huandu/xstrings v1.3.2
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba
@@ -103,6 +103,7 @@ require (
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v2 v2.4.0
+ lab.forgefriends.org/friendlyforgeformat/gof3 v0.0.0-20220906040547-8d2ab6ab8462
mvdan.cc/xurls/v2 v2.4.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
xorm.io/builder v0.3.11
@@ -157,6 +158,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
@@ -191,6 +193,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
@@ -281,6 +284,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-20220722155302-e5dcc9cfc0b9 // indirect
google.golang.org/appengine v1.6.7 // indirect
@@ -294,6 +298,10 @@ require (
sigs.k8s.io/yaml v1.2.0 // indirect
)
+//replace lab.forgefriends.org/friendlyforgeformat/gof3 => ../../gof3
+
+replace code.gitea.io/sdk/gitea => gitea.com/earl-warren/go-sdk/gitea v0.15.1-0.20220821233938-49e74ff9d331
+
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
diff --git a/go.sum b/go.sum
index 78b1df2c2..fc4f01712 100644
--- a/go.sum
+++ b/go.sum
@@ -64,12 +64,8 @@ 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=
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=
@@ -81,6 +77,8 @@ contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcig
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
+gitea.com/earl-warren/go-sdk/gitea v0.15.1-0.20220821233938-49e74ff9d331 h1:BZ8FPrsODvJeyT7AYK3JTqLK8qWLK4i+drUxqRs77Q4=
+gitea.com/earl-warren/go-sdk/gitea v0.15.1-0.20220821233938-49e74ff9d331/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb h1:Yy0Bxzc8R2wxiwXoG/rECGplJUSpXqCsog9PuJFgiHs=
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
@@ -369,6 +367,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=
@@ -481,6 +481,7 @@ github.com/go-enry/go-enry/v2 v2.8.2 h1:uiGmC+3K8sVd/6DOe2AOJEOihJdqda83nPyJNtMR
github.com/go-enry/go-enry/v2 v2.8.2/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=
@@ -1588,12 +1589,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.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
@@ -1609,6 +1612,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=
@@ -2241,6 +2246,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-20220906040547-8d2ab6ab8462 h1:uMa75Zn1Dgne2mPpO7JWsq5KgyyNandRoIq5KedaGk0=
+lab.forgefriends.org/friendlyforgeformat/gof3 v0.0.0-20220906040547-8d2ab6ab8462/go.mod h1:JOIXqRVTbUrY0e7nPUHFklu7gScEhioF7YD7Gfd5/4c=
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=
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 997899a90..486c427b0 100644
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -866,7 +866,7 @@ mirror_prune_desc=删除过时的远程跟踪引用
mirror_interval=镜像间隔 (有效的时间单位是 'h', 'm', 's')。0 禁用自动定期同步 (最短间隔: %s)
mirror_interval_invalid=镜像间隔无效。
mirror_sync_on_commit=推送提交时同步
-mirror_address=从URL克隆
+mirror_address=从 URL 克隆
mirror_address_desc=在授权框中输入必要的凭据。
mirror_address_url_invalid=URL无效。请检查您所输入的URL是否正确。
mirror_address_protocol_invalid=提供的 url 无效。只能从 http(s):// 或 git:// 位置进行镜像。
diff --git a/services/f3/driver/driver.go b/services/f3/driver/driver.go
new file mode 100644
index 000000000..dd1ce49f3
--- /dev/null
+++ b/services/f3/driver/driver.go
@@ -0,0 +1,104 @@
+// 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/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) Init(options gof3.OptionsInterface) {
+ o.SetOptions(options)
+ o.perPage = 100
+}
+
+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) common.ProviderInterface {
+ switch name {
+ case driver.ProviderUser:
+ return &driver.Provider[UserProvider, *UserProvider, User, *User, format.User, *format.User]{Impl: &UserProvider{g: o}}
+ case driver.ProviderProject:
+ return &driver.ProviderWithParentOne[ProjectProvider, *ProjectProvider, Project, *Project, format.Project, *format.Project, User, *User]{Impl: &ProjectProvider{g: o}}
+ // case driver.ProviderMilestone:
+ // return &driver.ProviderWithParentOneTwo[MilestoneProvider, *MilestoneProvider, Milestone, *Milestone, format.Milestone, *format.Milestone, User, *User, Project, *Project]{Impl: &MilestoneProvider{g: o}}
+ // case driver.ProviderIssue:
+ // return &driver.ProviderWithParentOneTwo[IssueProvider, *IssueProvider, Issue, *Issue, format.Issue, *format.Issue, User, *User, Project, *Project]{Impl: &IssueProvider{g: o}}
+ // case driver.ProviderPullRequest:
+ // return &driver.ProviderWithParentOneTwo[PullRequestProvider, *PullRequestProvider, PullRequest, *PullRequest, format.PullRequest, *format.PullRequest, User, *User, Project, *Project]{Impl: &PullRequestProvider{g: o}}
+ // case driver.ProviderReview:
+ // return &driver.ProviderWithParentOneTwoThree[ReviewProvider, *ReviewProvider, Review, *Review, format.Review, *format.Review, User, *User, Project, *Project, PullRequest, *PullRequest]{Impl: &ReviewProvider{g: o}}
+ // case driver.ProviderRepository:
+ // return &driver.ProviderWithParentOneTwo[RepositoryProvider, *RepositoryProvider, Repository, *Repository, format.Repository, *format.Repository, User, *User, Project, *Project]{Impl: &RepositoryProvider{g: o}}
+ // case driver.ProviderTopic:
+ // return &driver.ProviderWithParentOneTwo[TopicProvider, *TopicProvider, Topic, *Topic, format.Topic, *format.Topic, User, *User, Project, *Project]{Impl: &TopicProvider{g: o}}
+ // case driver.ProviderLabel:
+ // return &driver.ProviderWithParentOneTwo[LabelProvider, *LabelProvider, Label, *Label, format.Label, *format.Label, User, *User, Project, *Project]{Impl: &LabelProvider{g: o}}
+ // case driver.ProviderRelease:
+ // return &driver.ProviderWithParentOneTwo[ReleaseProvider, *ReleaseProvider, Release, *Release, format.Release, *format.Release, User, *User, Project, *Project]{Impl: &ReleaseProvider{g: o}}
+ // case driver.ProviderAsset:
+ // return &driver.ProviderWithParentOneTwoThree[AssetProvider, *AssetProvider, Asset, *Asset, format.ReleaseAsset, *format.ReleaseAsset, User, *User, Project, *Project, Release, *Release]{Impl: &AssetProvider{g: o}}
+ // case driver.ProviderComment:
+ // return &driver.ProviderWithParentOneTwoThreeInterface[CommentProvider, *CommentProvider, Comment, *Comment, format.Comment, *format.Comment, User, *User, Project, *Project]{Impl: &CommentProvider{g: o}}
+ default:
+ panic(fmt.Sprintf("unknown provider name %s", name))
+ }
+}
+
+func (o Gitea) Finish() {
+}
diff --git a/services/f3/driver/project.go b/services/f3/driver/project.go
new file mode 100644
index 000000000..b7f1e969e
--- /dev/null
+++ b/services/f3/driver/project.go
@@ -0,0 +1,151 @@
+// 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/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
+}
+
+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))
+ }
+ 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
+}
diff --git a/services/f3/driver/user.go b/services/f3/driver/user.go
new file mode 100644
index 000000000..a9c826227
--- /dev/null
+++ b/services/f3/driver/user.go
@@ -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"
+ 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/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) 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,
+ }
+ err := user_model.CreateUser(&user.User, overwriteDefault)
+ if err != nil {
+ panic(err)
+ }
+ return o.Get(user)
+}
+
+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
+}
diff --git a/services/f3/f3.go b/services/f3/f3.go
new file mode 100644
index 000000000..33efac50a
--- /dev/null
+++ b/services/f3/f3.go
@@ -0,0 +1,27 @@
+// 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 f3
+
+import (
+ "code.gitea.io/gitea/modules/log"
+ base "code.gitea.io/gitea/modules/migration"
+
+ "lab.forgefriends.org/friendlyforgeformat/gof3"
+)
+
+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,
+ }
+}
diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl
index fd1b60bef..69af36ac6 100644
--- a/templates/repo/home.tmpl
+++ b/templates/repo/home.tmpl
@@ -73,32 +73,33 @@
{{end}}
{{svg "octicon-file-moved" 15}}
- {{if or .CanAddFile .CanUploadFile}}
-
+ {{end}}
+ {{if ne $n 0}}
{{EllipsisString .Repository.Name 30}}{{range $i, $v := .TreeNames}}/{{if eq $i $l}}{{EllipsisString $v 30}}{{else}}{{$p := index $.Paths $i}}{{EllipsisString $v 30}}{{end}}{{end}}
{{end}}
diff --git a/tests/integration/f3_test.go b/tests/integration/f3_test.go
new file mode 100644
index 000000000..7a827ddbd
--- /dev/null
+++ b/tests/integration/f3_test.go
@@ -0,0 +1,81 @@
+// 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"
+ f3_driver "code.gitea.io/gitea/services/f3/driver"
+ "lab.forgefriends.org/friendlyforgeformat/gof3"
+ f3_forges "lab.forgefriends.org/friendlyforgeformat/gof3/forges"
+ f3_gitea "lab.forgefriends.org/friendlyforgeformat/gof3/forges/gitea"
+
+ "github.com/stretchr/testify/assert"
+)
+
+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
+ //
+ fixture := f3_forges.NewFixture(t, f3_forges.FixtureF3Factory)
+ fixture.NewUser()
+
+ //
+ // Step 2: mirror the fixture into Gitea
+ //
+ doer, err := user_model.GetAdminUser()
+ assert.NoError(t, err)
+
+ giteaLocal := f3_forges.NewForgeRootFromDriver(&f3_driver.Gitea{}, &f3_driver.Options{
+ Options: gof3.Options{
+ Configuration: gof3.Configuration{
+ Directory: t.TempDir(),
+ },
+ Features: gof3.AllFeatures,
+ },
+ Doer: doer,
+ })
+ giteaLocal.SetContext(context.Background())
+ giteaLocal.Forge.Mirror(fixture.GetForge())
+
+ //
+ // 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)
+ f3.Forge.Mirror(giteaAPI.Forge)
+
+ //
+ // Step 4: verify the fixture and F3 are equivalent
+ //
+ })
+}