Compare commits

...

28 commits

Author SHA1 Message Date
0766fa4d93
doc(schemas): labels & milestones are unique identifiers
All checks were successful
/ backend-checks (push) Successful in 2m32s
they previously were symbolic names but they were converted to be
unique identifiers (relative or absolute path to a label)
2024-05-24 01:25:40 +02:00
3bb63651ee
test: RandInt64 is deadcode
All checks were successful
/ backend-checks (push) Successful in 2m39s
2024-05-23 10:39:12 +02:00
e85bc74328
test(tree/generic): node.NewFormat & node.{Get,Set}Children
All checks were successful
/ backend-checks (push) Successful in 3m8s
2024-05-19 12:21:38 +02:00
bd73caef15
test(tree/generic): add unit tests for RemapReferences
All checks were successful
/ backend-checks (push) Successful in 6m4s
2024-05-18 14:49:23 +02:00
c78d690ffc
fix(tree/generic): add ErrorRemapReferencesRelative to RemapReferences
panic with a type so that the caller does not rely on a string that is
bound to have variance.
2024-05-18 14:46:41 +02:00
15d0212084
test(tree/generic): TreeCollectReferences deduplication coverage 2024-05-18 11:38:30 +02:00
80216f7dbb
cleanup(tree/generic): RemapReferences belongs to references.go 2024-05-18 11:18:51 +02:00
b50bd0f835
cleanup(tree/generic): redundant node.Get in NodeCollectReferences
The function is called from ApplyAndGet which already calls node.Get()
when necessary. Should it fail and GetIsSync() is false, a second call
immediately afterwards is not advisable because it will run the failed
retrieval of the object twice. Since it is not an intended behavior it
will only make debugging the source of the failure more difficult.
2024-05-18 11:10:34 +02:00
e27fea23b5
tests(tree/generic): add test coverage for {Tree,Node}Compare
All checks were successful
/ backend-checks (push) Successful in 2m34s
2024-05-17 11:43:52 +02:00
5c502de95d
cleanup(tree/generic): remove NodeCompare argument CompareOptions
It was initially added to fine tune how in memory trees are
compared. It turns out field exclusions specified on a per-format
basis using cmp.Options is more flexible.
2024-05-17 11:39:09 +02:00
851fa99b4b
tests(tree/generic): path coverage
All checks were successful
/ backend-checks (push) Successful in 2m34s
2024-05-16 14:30:13 +02:00
5c0cc8631f
cleanup(tree/generic): s/PathStringRelative/PathRelativeString/ 2024-05-16 13:51:45 +02:00
5e1133b97e
cleanup(tree/generic): path.Clone() is a noop 2024-05-16 13:51:42 +02:00
a3e995c75e
feat(tree/generic): filepath.Clean the return of PathAbsoluteString 2024-05-16 13:48:21 +02:00
28944e0dca
test(tree/generic): TreeCollectReferences & NodeCollectReferences
All checks were successful
/ backend-checks (push) Successful in 2m51s
2024-05-14 12:35:09 +02:00
f71db5044e
fix(tree/generic): remove deadcode
ApplyAndGet will new fails to find a node because it will create it
when it does not exist.
2024-05-14 12:33:58 +02:00
c865dcc8bc
test(tree/generic): Apply & ApplyAndGet
All checks were successful
/ backend-checks (push) Successful in 2m32s
2024-05-14 11:20:50 +02:00
3e08a81fa2
test(tree/generic): do not export test functions 2024-05-14 11:16:21 +02:00
ea227d0968
test(tree/generic): AllocateID 2024-05-14 07:52:00 +02:00
c8eb857ad7
chore(deadcode): remove util/uri
All checks were successful
/ backend-checks (push) Successful in 2m31s
2024-05-14 07:24:08 +02:00
5422888cf8
chore(lint): run deadcode 2024-05-14 07:23:12 +02:00
9608b31425
chore: spelling 2024-05-14 06:40:43 +02:00
27c27c9791
uri: add coverage
All checks were successful
/ backend-checks (push) Successful in 2m30s
2024-05-12 23:36:23 +02:00
fa11abf29a
chore(lint): update .golangci.yml to clear warnings 2024-05-12 23:35:51 +02:00
553d9a9eeb
chore(dependencies): run misspell
All checks were successful
/ backend-checks (push) Successful in 2m27s
2024-05-12 22:48:01 +02:00
f9c831d756
f3: repositories are references in pull requests
All checks were successful
/ backend-checks (push) Successful in 2m22s
Representing repositories as owner and project IDs is problematic
because it assumes:

* a repository is at a specific location
* a repository location is defined by a pair of identifiers

Replace these two values with a reference string (absolute or
relative) to the actual repository.

Refs: https://forum.forgefriends.org/t/f3-monthly-update-april-2024/1024#references-resolution-3
2024-05-11 12:22:45 +02:00
79a6d17044
f3: newCmpCompareOptions is private NewCmpOptions is public
All checks were successful
/ backend-checks (push) Successful in 2m26s
2024-05-11 12:17:20 +02:00
7d5812277c
tree(generic): use nothing as a placeholder when the id is missing
All checks were successful
/ backend-checks (push) Successful in 2m38s
Only the root is the empty string. Using the empty string leads to
confusing errors when normalizing the path because /foo/bar//frob is
the same as /foo/bar/frob and it won't be clear that an identifier was
missing in the first place.
2024-05-10 18:20:11 +02:00
42 changed files with 906 additions and 300 deletions

74
.deadcode-out Normal file
View file

@ -0,0 +1,74 @@
code.forgejo.org/f3/gof3/v3/api
TreeMirror
code.forgejo.org/f3/gof3/v3/f3
RepositoryDirname
code.forgejo.org/f3/gof3/v3/forges/forgejo
common.isContainer
webClients.impersonate
treeDriver.getWebClient
code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk
hasAgent
GetAgent
Version
NewClientWithHTTP
SetHTTPClient
UseSSHCert
UseSSHPubkey
SetOTP
SetContext
SetSudo
SetUserAgent
SetDebugMode
OptionalBool
OptionalString
OptionalInt64
VerifyWebhookSignature
VerifyWebhookSignatureMiddleware
NewHTTPSignWithPubkey
NewHTTPSignWithCert
newHTTPSign
findCertSigner
findPubkeySigner
SetGiteaVersion
code.forgejo.org/f3/gof3/v3/forges/helpers/tests/repository
TestHelper.GetNode
TestHelper.RevList
TestHelper.AssertRepositoryNotFileExists
TestHelper.BranchRepositoryFeature
code.forgejo.org/f3/gof3/v3/options/cli
OptionsCLI.FromFlags
OptionsCLI.GetFlags
code.forgejo.org/f3/gof3/v3/tree/f3
NewLabelReference
NewPullRequestLabelReference
NewMilestoneReference
NewPath
pullRequestNode.GetPullRequestHead
pullRequestNode.GetPullRequestRef
pullRequestNode.GetPullRequestPushRefs
newPullRequestNode
NewRepositoryPath
NewTopicPath
NewTopicPathString
NewTopicReference
code.forgejo.org/f3/gof3/v3/tree/f3/objects
FuncReadURLAndSetSHA
code.forgejo.org/f3/gof3/v3/tree/generic
MirrorOptions.SetNoRemap
TreePartialMirror
code.forgejo.org/f3/gof3/v3/tree/tests/f3
Creator.GetDirectory
Creator.Generate
code.forgejo.org/f3/gof3/v3/util/tests
CompareEqualValues

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ coverage.out
coverage.html
tests/*.out
format/schemas/.gitignore
.cur-deadcode-out

View file

@ -27,9 +27,11 @@ linters:
fast: false
run:
go: 1.21
go: 1.22
timeout: 10m
skip-dirs:
issues:
exclude-dirs:
- forges/forgejo/sdk
linters-settings:
@ -69,8 +71,6 @@ linters-settings:
- name: errorf
- name: duplicated-imports
- name: modifies-value-receiver
gofumpt:
lang-version: "1.21"
depguard:
#list-type: denylist
# Check the list against standard lib.

View file

@ -3,12 +3,16 @@ GO ?= go
BINDATA_DEST := format/bindata.go
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.5.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56.1
GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10
DIFF ?= diff --unified
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v2/cmd/editorconfig-checker@2.8.0 # renovate: datasource=go
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0 # renovate: datasource=go
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2 # renovate: datasource=go
UNCOVER_PACKAGE ?= github.com/gregoryv/uncover/cmd/uncover@latest
MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1 # renovate: datasource=go
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.21.0 # renovate: datasource=go
DEADCODE_ARGS ?= -generated=false -test -f='{{println .Path}}{{range .Funcs}}{{printf "\t%s\n" .Name}}{{end}}{{println}}' code.forgejo.org/f3/gof3/v3/...
VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
@ -33,14 +37,36 @@ deps-backend:
$(GO) mod download
$(GO) install $(GOFUMPT_PACKAGE)
$(GO) install $(GOLANGCI_LINT_PACKAGE)
$(GO) install $(GXZ_PAGAGE)
$(GO) install $(UNCOVER_PACKAGE)
$(GO) install $(MISSPELL_PACKAGE)
$(GO) install $(DEADCODE_PACKAGE)
.PHONY: lint
lint:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run
@if ! $(MAKE) lint-run ; then echo "Please run 'make lint-fix' and commit the result" ; exit 1 ; fi
.PHONY: lint-run
lint-run:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS)
$(GO) run $(DEADCODE_PACKAGE) $(DEADCODE_ARGS) > .cur-deadcode-out
$(DIFF) .deadcode-out .cur-deadcode-out
.PHONY: lint-fix
lint-fix:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) --fix
$(GO) run $(DEADCODE_PACKAGE) $(DEADCODE_ARGS) > .deadcode-out
.PHONY: fmt
fmt:
MISSPELL_PACKAGE=$(MISSPELL_PACKAGE) GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) gofumpt -extra -w .
GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) gofumpt -extra -w .
SPELLCHECK_FILES = $(GO_DIRS)
.PHONY: lint-spell
lint-spell:
@go run $(MISSPELL_PACKAGE) -error $(SPELLCHECK_FILES)
.PHONY: lint-spell-fix
lint-spell-fix:
@go run $(MISSPELL_PACKAGE) -w $(SPELLCHECK_FILES)

View file

@ -13,15 +13,13 @@
"clone_url": "head_clone_url",
"ref": "head_branch",
"sha": "head_sha",
"repo_name": "repo_a",
"owner_name": "owner_a"
"repository": "/forge/user/1/projects/2/repositories/vcs"
},
"base": {
"clone_url": "base_clone_url",
"ref": "base_branch",
"sha": "base _sha",
"repo_name": "repo_b",
"owner_name": "owner_b"
"repository": "/forge/user/3/projects/4/repositories/vcs"
},
"labels": ["label1"]
}

View file

@ -93,7 +93,7 @@ func (c *Common) ToReference() *Reference { return &c.Index }
func (c *Common) GetCmpIgnoreFields() []string { return []string{".Index"} }
func (c *Common) GetComparers() []Comparer { return []Comparer{} }
func NewCmpCompareOptions(f Interface) cmp.Options {
func newCmpCompareOptions(f Interface) cmp.Options {
options := make(cmp.Options, 0, 5)
for _, comparer := range f.GetComparers() {
options = append(options, cmp.Comparer(comparer))
@ -110,7 +110,7 @@ func NewCmpOptions(f Interface) cmp.Options {
},
cmp.Ignore()),
}
return append(options, NewCmpCompareOptions(f)...)
return append(options, newCmpCompareOptions(f)...)
}
var Nil = &Common{}

View file

@ -42,15 +42,17 @@ func (o *PullRequest) GetReferences() References {
if o.Milestone != nil {
references = append(references, o.Milestone)
}
references = append(references, o.Base.GetReferences()...)
references = append(references, o.Head.GetReferences()...)
return append(references, o.PosterID)
}
func (o *PullRequest) IsForkPullRequest() bool {
return o.Head.RepoPath() != o.Base.RepoPath()
return o.Head.Repository != o.Base.Repository
}
func (o PullRequest) GetCmpIgnoreFields() []string {
return append(o.Common.GetCmpIgnoreFields(), ".FetchFunc", ".Updated", ".Created", ".Closed")
return append(o.Common.GetCmpIgnoreFields(), ".FetchFunc", ".Updated", ".Created", ".Closed", ".Repository")
}
func (o *PullRequest) Clone() Interface {

View file

@ -4,17 +4,12 @@
package f3
import (
"fmt"
)
type PullRequestBranch struct {
Ref string `json:"ref"`
SHA string `json:"sha"`
OwnerName string `json:"owner_name"`
RepoName string `json:"repo_name"`
Ref string `json:"ref"`
SHA string `json:"sha"`
Repository *Reference `json:"repository"`
}
func (o PullRequestBranch) RepoPath() string {
return fmt.Sprintf("%s/%s", o.OwnerName, o.RepoName)
func (o *PullRequestBranch) GetReferences() References {
return References{o.Repository}
}

View file

@ -75,7 +75,7 @@
]
},
"labels": {
"description": "List of labels.",
"description": "List of label unique identifiers.",
"anyOf": [
{
"type": "array",

View file

@ -22,7 +22,7 @@
"type": "string"
},
"milestone": {
"description": "Name of the milestone.",
"description": "Unique identifier of the milestone.",
"type": "string"
},
"state": {
@ -59,7 +59,7 @@
]
},
"labels": {
"description": "List of labels.",
"description": "List of labels unique identifiers.",
"anyOf": [
{
"type": "array",

View file

@ -13,20 +13,15 @@
"description": "SHA of the commit.",
"type": "string"
},
"repo_name": {
"description": "Name of the project that contains the repository.",
"type": "string"
},
"owner_name": {
"description": "Name of the user or organization that contains the project.",
"repository": {
"description": "Unique identifier of the repository.",
"type": "string"
}
},
"required": [
"ref",
"sha",
"repo_name",
"owner_name"
"repository"
],
"$schema": "http://json-schema.org/draft-04/schema#",

View file

@ -10,7 +10,7 @@
"type": "string"
},
"reviewer_id": {
"description": "Unique identifer of review author.",
"description": "Unique identifier of review author.",
"type": "string"
},
"official": {

View file

@ -60,7 +60,7 @@ func (o *nodeDriver) IsNull() bool { return false }
func (o *nodeDriver) GetIDFromName(ctx context.Context, name string) generic.NodeID {
switch o.getKind() {
case generic.KindRoot, f3_tree.KindProjects, f3_tree.KindUsers, f3_tree.KindOrganizations:
case generic.KindRoot, f3_tree.KindProjects, f3_tree.KindUsers, f3_tree.KindOrganizations, f3_tree.KindRepositories:
default:
panic(fmt.Errorf("unxpected kind %s", o.getKind()))
}

View file

@ -43,6 +43,10 @@ func (o *common) getKind() generic.Kind {
return o.GetNode().GetKind()
}
func (o *common) getChildDriver(kind generic.Kind) generic.NodeDriverInterface {
return o.GetNode().GetChild(generic.NodeID(kind)).GetDriver()
}
func (o *common) isContainer() bool {
return o.getF3Tree().IsContainer(o.getKind())
}

View file

@ -6,17 +6,52 @@ package forgejo
import (
"context"
"fmt"
"strings"
"code.forgejo.org/f3/gof3/v3/f3"
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
)
type forge struct {
generic.NullDriver
common
ownersKind map[string]generic.Kind
}
func newForge() generic.NodeDriverInterface {
return &forge{}
return &forge{
ownersKind: make(map[string]generic.Kind),
}
}
func (o *forge) getOwnersKind(ctx context.Context, id string) generic.Kind {
kind, ok := o.ownersKind[id]
if !ok {
_, _, err := o.getClient().GetUserByID(util.ParseInt(id))
if err != nil {
if strings.Contains(err.Error(), "user not found") {
organizations := o.getChildDriver(f3_tree.KindOrganizations).(*organizations)
if organizations.getNameFromID(ctx, util.ParseInt(id)) != "" {
kind = f3_tree.KindOrganizations
} else {
panic(fmt.Errorf("%v does not match any user or organization", id))
}
} else {
panic(fmt.Errorf("GetUserByID(%s) failed %w", id, err))
}
} else {
kind = f3_tree.KindUsers
}
o.ownersKind[id] = kind
}
return kind
}
func (o *forge) getOwnersPath(ctx context.Context, id string) f3_tree.Path {
return f3_tree.NewPathFromString("/").SetForge().SetOwners(o.getOwnersKind(ctx, id))
}
func (o *forge) Equals(context.Context, generic.NodeInterface) bool { return true }

View file

@ -21,6 +21,8 @@ type pullRequest struct {
common
forgejoPullRequest *forgejo_sdk.PullRequest
headRepository *f3.Reference
baseRepository *f3.Reference
fetchFunc f3.PullRequestFetchFunc
}
@ -43,6 +45,33 @@ func (o *pullRequest) NewFormat() f3.Interface {
return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}
func (o *pullRequest) repositoryToReference(ctx context.Context, repository *forgejo_sdk.Repository) *f3.Reference {
if repository == nil {
panic("unexpected nil repository")
}
forge := o.getTree().GetRoot().GetChild(f3_tree.KindForge).GetDriver().(*forge)
owners := forge.getOwnersPath(ctx, fmt.Sprintf("%d", repository.Owner.ID))
return f3_tree.NewRepositoryReference(owners.String(), repository.Owner.ID, repository.ID)
}
func (o *pullRequest) referenceToRepository(reference *f3.Reference) *forgejo_sdk.Repository {
var owner, project int64
if reference.Get() == "../../repository/vcs" {
project = f3_tree.GetProjectID(o.GetNode())
owner = f3_tree.GetOwnerID(o.GetNode())
} else {
p := f3_tree.ToPath(generic.PathAbsolute(o.GetNode().GetCurrentPath().String(), reference.Get()))
o.Trace("%v %v", o.GetNode().GetCurrentPath().String(), p)
owner, project = p.OwnerAndProjectID()
}
return &forgejo_sdk.Repository{
ID: project,
Owner: &forgejo_sdk.User{
ID: owner,
},
}
}
func (o *pullRequest) ToFormat() f3.Interface {
if o.forgejoPullRequest == nil {
return o.NewFormat()
@ -63,31 +92,19 @@ func (o *pullRequest) ToFormat() f3.Interface {
}
var (
headUserName string
headRepoName string
headRef string
headSHA string
headRef string
headSHA string
)
if o.forgejoPullRequest.Head != nil {
if o.forgejoPullRequest.Head.Repository != nil {
headUserName = o.forgejoPullRequest.Head.Repository.Owner.UserName
headRepoName = o.forgejoPullRequest.Head.Repository.Name
}
headSHA = o.forgejoPullRequest.Head.Sha
headRef = o.forgejoPullRequest.Head.Ref
}
var (
baseUserName string
baseRepoName string
baseRef string
baseSHA string
baseRef string
baseSHA string
)
if o.forgejoPullRequest.Base != nil {
if o.forgejoPullRequest.Base.Repository != nil {
baseUserName = o.forgejoPullRequest.Base.Repository.Owner.UserName
baseRepoName = o.forgejoPullRequest.Base.Repository.Name
}
baseSHA = o.forgejoPullRequest.Base.Sha
baseRef = o.forgejoPullRequest.Base.Ref
}
@ -117,16 +134,14 @@ func (o *pullRequest) ToFormat() f3.Interface {
MergeCommitSHA: mergeCommitSHA,
IsLocked: o.forgejoPullRequest.IsLocked,
Head: f3.PullRequestBranch{
Ref: headRef,
SHA: headSHA,
RepoName: headRepoName,
OwnerName: headUserName,
Ref: headRef,
SHA: headSHA,
Repository: o.headRepository,
},
Base: f3.PullRequestBranch{
Ref: baseRef,
SHA: baseSHA,
RepoName: baseRepoName,
OwnerName: baseUserName,
Ref: baseRef,
SHA: baseSHA,
Repository: o.baseRepository,
},
FetchFunc: o.fetchFunc,
}
@ -156,24 +171,14 @@ func (o *pullRequest) FromFormat(content f3.Interface) {
Merged: pullRequest.MergedTime,
MergedCommitID: &pullRequest.MergeCommitSHA,
Base: &forgejo_sdk.PRBranchInfo{
Ref: pullRequest.Base.Ref,
Sha: pullRequest.Base.SHA,
Repository: &forgejo_sdk.Repository{
Name: pullRequest.Base.RepoName,
Owner: &forgejo_sdk.User{
UserName: pullRequest.Base.OwnerName,
},
},
Ref: pullRequest.Base.Ref,
Sha: pullRequest.Base.SHA,
Repository: o.referenceToRepository(pullRequest.Base.Repository),
},
Head: &forgejo_sdk.PRBranchInfo{
Ref: pullRequest.Head.Ref,
Sha: pullRequest.Head.SHA,
Repository: &forgejo_sdk.Repository{
Name: pullRequest.Head.RepoName,
Owner: &forgejo_sdk.User{
UserName: pullRequest.Head.OwnerName,
},
},
Ref: pullRequest.Head.Ref,
Sha: pullRequest.Head.SHA,
Repository: o.referenceToRepository(pullRequest.Head.Repository),
},
Created: &pullRequest.Created,
Updated: &pullRequest.Updated,
@ -186,16 +191,19 @@ func (o *pullRequest) Get(ctx context.Context) bool {
node := o.GetNode()
o.Trace("%s", node.GetID())
owner := f3_tree.GetOwnerName(o.GetNode())
project := f3_tree.GetProjectName(o.GetNode())
ownerName := f3_tree.GetOwnerName(o.GetNode())
projectName := f3_tree.GetProjectName(o.GetNode())
pr, resp, err := o.getClient().GetPullRequest(owner, project, util.ParseInt(string(node.GetID())))
pr, resp, err := o.getClient().GetPullRequest(ownerName, projectName, util.ParseInt(string(node.GetID())))
if resp.StatusCode == 404 {
return false
}
if err != nil {
panic(fmt.Errorf("pullRequest %v %w", o, err))
}
o.headRepository = o.repositoryToReference(ctx, o.forgejoPullRequest.Head.Repository)
o.baseRepository = o.repositoryToReference(ctx, o.forgejoPullRequest.Base.Repository)
o.forgejoPullRequest = pr
return true
}

View file

@ -60,8 +60,7 @@ func (o *treeF3) CreateChild(ctx context.Context, pathString string, set setFunc
o.Trace("%s %s | %T %s", parent.String(), node.GetID(), node, node.GetKind())
child := o.Factory(ctx, o.GetChildrenKind(node.GetKind()))
child.SetParent(node)
childParentPath := parent.Clone()
childParentPath = childParentPath.Append(node)
childParentPath := parent.Append(node)
if set != nil {
set(childParentPath, child)
}
@ -91,7 +90,7 @@ func newTreeF3(ctx context.Context, opts options.Interface) generic.TreeInterfac
return newFixedChildrenNode(ctx, tree, []generic.Kind{KindProjects})
})
tree.Register(KindProject, func(ctx context.Context, kind generic.Kind) generic.NodeInterface {
return newFixedChildrenNode(ctx, tree, []generic.Kind{KindRepositories, KindIssues, KindLabels, KindMilestones, KindPullRequests, KindReleases})
return newFixedChildrenNode(ctx, tree, []generic.Kind{KindRepositories, KindLabels, KindMilestones, KindIssues, KindPullRequests, KindReleases})
})
tree.Register(KindIssue, func(ctx context.Context, kind generic.Kind) generic.NodeInterface {
return newFixedChildrenNode(ctx, tree, []generic.Kind{KindComments, KindReactions})

View file

@ -9,11 +9,12 @@ import (
"slices"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
)
type Path interface {
NodeIDs() []generic.NodeID
OwnerAndProject() (owner, project generic.NodeID)
OwnerAndProjectID() (owner, project int64)
AppendID(id string) Path
Ignore() Path
@ -39,6 +40,9 @@ type Path interface {
Milestones() Path
SetMilestones() Path
Owners() Path
SetOwners(owners generic.Kind) Path
Organizations() Path
SetOrganizations() Path
@ -103,9 +107,9 @@ func (o path) NodeIDs() []generic.NodeID {
return nodeIDs
}
func (o path) OwnerAndProject() (owner, project generic.NodeID) {
func (o path) OwnerAndProjectID() (owner, project int64) {
nodeIDs := o.NodeIDs()
return nodeIDs[0], nodeIDs[1]
return util.ParseInt(string(nodeIDs[0])), util.ParseInt(string(nodeIDs[1]))
}
func (o path) Root() Path { return o.popKind(generic.Kind("")) }
@ -158,7 +162,8 @@ func (o path) SetTopics() Path { return o.appendKind(KindTopics) }
func (o path) Users() Path { return o.popKind(KindUsers) }
func (o path) SetUsers() Path { return o.appendKind(KindUsers) }
func (o path) Owners() Path { return o.popKind(KindUsers, KindOrganizations) }
func (o path) Owners() Path { return o.popKind(KindUsers, KindOrganizations) }
func (o path) SetOwners(owners generic.Kind) Path { return o.appendKind(owners) }
func (o path) AppendID(id string) Path {
return ToPath(o.Append(generic.NewNodeFromID(id)))

View file

@ -8,6 +8,7 @@ import (
"testing"
"code.forgejo.org/f3/gof3/v3/tree/generic"
"code.forgejo.org/f3/gof3/v3/util"
"github.com/stretchr/testify/assert"
)
@ -20,8 +21,9 @@ func TestF3Path(t *testing.T) {
}
nodeIDs := make([]generic.NodeID, 0, 10)
{
p := p.SetUsers()
id := "user1"
p := p.SetOwners(KindUsers)
id := "1"
ownerID := util.ParseInt(id)
nodeIDs := append(nodeIDs, generic.NodeID(id))
p = p.AppendID(id)
assert.EqualValues(t, generic.NodeID(id), p.Root().Forge().Users().First().GetID())
@ -29,14 +31,15 @@ func TestF3Path(t *testing.T) {
{
p := p.SetProjects()
id := "project1"
id := "2"
projectID := util.ParseInt(id)
nodeIDs := append(nodeIDs, generic.NodeID(id))
p = p.AppendID(id)
assert.EqualValues(t, generic.NodeID(id), p.Root().Forge().Users().Ignore().Projects().First().GetID())
assert.EqualValues(t, nodeIDs, p.NodeIDs())
owner, project := p.OwnerAndProject()
assert.EqualValues(t, nodeIDs[0], owner)
assert.EqualValues(t, nodeIDs[1], project)
owner, project := p.OwnerAndProjectID()
assert.EqualValues(t, ownerID, owner)
assert.EqualValues(t, projectID, project)
}
}

View file

@ -6,7 +6,9 @@ package f3
import (
"context"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/tree/generic"
)
@ -40,3 +42,19 @@ func newRepositoryNode(ctx context.Context, tree generic.TreeInterface) generic.
node := &repositoryNode{}
return node.Init(node)
}
func NewRepositoryPath[T, U any](owners string, owner T, project U) generic.Path {
return generic.NewPathFromString(NewRepositoryPathString(owners, owner, project))
}
func NewRepositoryPathString[T, U any](owners string, owner T, project U) string {
return fmt.Sprintf("%s/%v/projects/%v/repositories/vcs", owners, owner, project)
}
func NewRepositoryReference[T, U any](owners string, owner T, project U) *f3.Reference {
return f3.NewReference(NewRepositoryPathString(owners, owner, project))
}
func NewPullRequestSameRepositoryReference() *f3.Reference {
return f3.NewReference("../../repositories/vcs")
}

View file

@ -8,13 +8,7 @@ import (
"context"
)
type CompareOptions struct{}
func NewCompareOptions() *CompareOptions {
return &CompareOptions{}
}
func NodeCompare(ctx context.Context, a, b NodeInterface, options *CompareOptions) bool {
func NodeCompare(ctx context.Context, a, b NodeInterface) bool {
a.Trace("a '%s' | b '%s'", a.GetCurrentPath(), b.GetCurrentPath())
if a.GetKind() != b.GetKind() {
@ -48,7 +42,7 @@ func NodeCompare(ctx context.Context, a, b NodeInterface, options *CompareOption
return false
}
if !NodeCompare(ctx, aChild, bChild, options) {
if !NodeCompare(ctx, aChild, bChild) {
return false
}
}
@ -56,7 +50,7 @@ func NodeCompare(ctx context.Context, a, b NodeInterface, options *CompareOption
return true
}
func TreeCompare(ctx context.Context, aTree TreeInterface, aPath Path, bTree TreeInterface, bPath Path, options *CompareOptions) bool {
func TreeCompare(ctx context.Context, aTree TreeInterface, aPath Path, bTree TreeInterface, bPath Path) bool {
aTree.Trace("'%s' => '%s'", aPath, bPath)
a := aTree.Find(aPath)
@ -71,5 +65,5 @@ func TreeCompare(ctx context.Context, aTree TreeInterface, aPath Path, bTree Tre
return false
}
return NodeCompare(ctx, a, b, options)
return NodeCompare(ctx, a, b)
}

View file

@ -0,0 +1,223 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"testing"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/logger"
"github.com/stretchr/testify/assert"
)
type compareFormat struct {
f3.Common
V int
}
func (o *compareFormat) Clone() f3.Interface {
clone := &compareFormat{}
*clone = *o
return clone
}
type compareNodeDriver struct {
NullDriver
v int
ID string
}
func (o *compareNodeDriver) NewFormat() f3.Interface {
return &compareFormat{
Common: f3.NewCommon(o.ID),
}
}
func (o *compareNodeDriver) ToFormat() f3.Interface {
f := o.NewFormat().(*compareFormat)
f.V = o.v
return f
}
func (o *compareNodeDriver) FromFormat(f f3.Interface) {
fc := f.(*compareFormat)
o.v = fc.V
o.ID = fc.GetID()
}
func newCompareNodeDriver() NodeDriverInterface {
return &compareNodeDriver{}
}
type compareTreeDriver struct {
NullTreeDriver
}
func newCompareTreeDriver() TreeDriverInterface {
return &compareTreeDriver{}
}
func (o *compareTreeDriver) Factory(ctx context.Context, kind Kind) NodeDriverInterface {
d := newCompareNodeDriver()
d.SetTreeDriver(o)
return d
}
func newCompareTree() TreeInterface {
tree := &testTree{}
tree.Init(tree, newTestOptions())
tree.Trace("init done")
tree.SetDriver(newCompareTreeDriver())
tree.Register(kindCompareNode, func(ctx context.Context, kind Kind) NodeInterface {
node := &compareNode{}
return node.Init(node)
})
return tree
}
var kindCompareNode = Kind("compare")
type compareNode struct {
Node
}
func TestTreeCompare(t *testing.T) {
tree := newTestTree()
log := logger.NewCaptureLogger()
log.SetLevel(logger.Trace)
tree.SetLogger(log)
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
tree.SetRoot(root)
assert.True(t, TreeCompare(context.Background(), tree, NewPathFromString(""), tree, NewPathFromString("")))
log.Reset()
assert.False(t, TreeCompare(context.Background(), tree, NewPathFromString("/notfound"), tree, NewPathFromString("")))
assert.Contains(t, log.String(), "a does not have /notfound")
log.Reset()
assert.False(t, TreeCompare(context.Background(), tree, NewPathFromString("/"), tree, NewPathFromString("/notfound")))
assert.Contains(t, log.String(), "b does not have /notfound")
}
func TestNodeCompare(t *testing.T) {
log := logger.NewCaptureLogger()
log.SetLevel(logger.Trace)
treeA := newCompareTree()
treeA.SetLogger(log)
treeB := newCompareTree()
treeB.SetLogger(log)
nodeA := treeA.Factory(context.Background(), kindCompareNode)
nodeA.SetID("root")
assert.True(t, NodeCompare(context.Background(), nodeA, nodeA))
t.Run("different kind", func(t *testing.T) {
log.Reset()
other := treeB.Factory(context.Background(), kindCompareNode)
other.SetKind("other")
assert.False(t, NodeCompare(context.Background(), nodeA, other))
assert.Contains(t, log.String(), "kind is different")
})
t.Run("difference", func(t *testing.T) {
log.Reset()
other := treeB.Factory(context.Background(), kindCompareNode)
other.FromFormat(&compareFormat{V: 123456})
assert.False(t, NodeCompare(context.Background(), nodeA, other))
assert.Contains(t, log.String(), "difference")
assert.Contains(t, log.String(), "123456")
})
t.Run("children count", func(t *testing.T) {
log.Reset()
other := treeB.Factory(context.Background(), kindCompareNode)
other.SetChild(treeB.Factory(context.Background(), kindCompareNode))
assert.False(t, NodeCompare(context.Background(), nodeA, other))
assert.Contains(t, log.String(), "children count")
})
nodeAA := treeA.Factory(context.Background(), kindCompareNode)
nodeAA.SetID("levelone")
nodeA.SetChild(nodeAA)
nodeB := treeB.Factory(context.Background(), kindCompareNode)
nodeB.SetID("root")
t.Run("children are the same", func(t *testing.T) {
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
nodeBB.SetID("levelone")
nodeB.SetChild(nodeBB)
assert.True(t, NodeCompare(context.Background(), nodeA, nodeB))
nodeB.DeleteChild(nodeBB.GetID())
})
t.Run("children have different IDs", func(t *testing.T) {
log.Reset()
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
nodeBB.SetID("SOMETHINGELSE")
nodeB.SetChild(nodeBB)
assert.False(t, NodeCompare(context.Background(), nodeA, nodeB))
assert.Contains(t, log.String(), "id levelone matching the child")
nodeB.DeleteChild(nodeBB.GetID())
})
t.Run("children have different content", func(t *testing.T) {
log.Reset()
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
nodeBB.FromFormat(&compareFormat{V: 12345678})
nodeBB.SetID("levelone")
nodeB.SetChild(nodeBB)
assert.False(t, NodeCompare(context.Background(), nodeA, nodeB))
assert.Contains(t, log.String(), "difference")
assert.Contains(t, log.String(), "12345678")
nodeB.DeleteChild(nodeBB.GetID())
})
t.Run("children are the same because of their mapped ID", func(t *testing.T) {
log.Reset()
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
nodeBB.SetID("REMAPPEDID")
nodeB.SetChild(nodeBB)
nodeAA.SetMappedID("REMAPPEDID")
assert.True(t, NodeCompare(context.Background(), nodeA, nodeB))
nodeB.DeleteChild(nodeBB.GetID())
nodeAA.SetMappedID(NilID)
})
t.Run("children are different because of their mapped ID", func(t *testing.T) {
log.Reset()
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
nodeBB.SetID("levelone")
nodeB.SetChild(nodeBB)
nodeAA.SetMappedID("REMAPPEDID")
assert.False(t, NodeCompare(context.Background(), nodeA, nodeB))
assert.Contains(t, log.String(), "id REMAPPEDID matching the child")
nodeB.DeleteChild(nodeBB.GetID())
nodeAA.SetMappedID(NilID)
})
}

View file

@ -70,7 +70,7 @@ func (o *noopNodeDriver) FromFormat(f f3.Interface) {
o.GetNode().SetID(NodeID(f.GetID()))
}
func newNoopNodeDriver() NodeDriverInterface {
func newTestNodeDriver() NodeDriverInterface {
return &noopNodeDriver{}
}
@ -83,22 +83,22 @@ func newTestTreeDriver() TreeDriverInterface {
}
func (o *testTreeDriver) Factory(ctx context.Context, kind Kind) NodeDriverInterface {
d := newNoopNodeDriver()
d := newTestNodeDriver()
d.SetTreeDriver(o)
return d
}
func NewTestTree() TreeInterface {
func newTestTree() TreeInterface {
tree := &testTree{}
tree.Init(tree, newTestOptions())
tree.Trace("init done")
tree.SetDriver(newTestTreeDriver())
tree.Register(KindTestNodeLevelOne, func(ctx context.Context, kind Kind) NodeInterface {
node := &TestNodeLevelOne{}
tree.Register(kindTestNodeLevelOne, func(ctx context.Context, kind Kind) NodeInterface {
node := &testNodeLevelOne{}
return node.Init(node)
})
tree.Register(KindTestNodeLevelTwo, func(ctx context.Context, kind Kind) NodeInterface {
node := &TestNodeLevelTwo{}
tree.Register(kindTestNodeLevelTwo, func(ctx context.Context, kind Kind) NodeInterface {
node := &testNodeLevelTwo{}
return node.Init(node)
})
return tree
@ -124,19 +124,14 @@ func (o *testNode) Equals(ctx context.Context, other NodeInterface) bool {
return o.GetV() == other.(testNodeInterface).GetV()
}
func NewTestNode(v int) NodeInterface {
node := &testNode{v: v}
return node.Init(node)
}
var kindTestNodeLevelOne = Kind("levelone")
var KindTestNodeLevelOne = Kind("levelone")
type TestNodeLevelOne struct {
type testNodeLevelOne struct {
testNode
}
var KindTestNodeLevelTwo = Kind("leveltwo")
var kindTestNodeLevelTwo = Kind("leveltwo")
type TestNodeLevelTwo struct {
type testNodeLevelTwo struct {
testNode
}

View file

@ -41,14 +41,14 @@ func TestNodeIsNil(t *testing.T) {
assert.True(t, node.GetIsNil())
}
func TestNodeCompare(t *testing.T) {
func TestNodeEquals(t *testing.T) {
ctx := context.Background()
tree := NewTestTree()
one := tree.Factory(ctx, KindTestNodeLevelOne)
one.(*TestNodeLevelOne).v = 1
tree := newTestTree()
one := tree.Factory(ctx, kindTestNodeLevelOne)
one.(*testNodeLevelOne).v = 1
assert.True(t, one.Equals(ctx, one))
two := tree.Factory(ctx, KindTestNodeLevelOne)
two.(*TestNodeLevelOne).v = 2
two := tree.Factory(ctx, kindTestNodeLevelOne)
two.(*testNodeLevelOne).v = 2
assert.False(t, one.Equals(ctx, two))
}
@ -77,6 +77,40 @@ func TestNodeMappedID(t *testing.T) {
assert.True(t, mapped == node.GetMappedID())
}
func TestNodeNewFormat(t *testing.T) {
node := NewNode()
node.SetDriver(NewNullDriver())
assert.Panics(t, func() { node.NewFormat() })
}
func TestChildren(t *testing.T) {
parent := NewNode()
assert.Empty(t, parent.GetChildren())
id1 := NodeID("one")
child1 := NewNode()
child1.SetID(id1)
parent.SetChild(child1)
id2 := NodeID("two")
child2 := NewNode()
child2.SetID(id2)
parent.SetChild(child2)
children := parent.GetChildren()
assert.Len(t, children, 2)
assert.Equal(t, children[0].GetID(), id1)
assert.Equal(t, children[1].GetID(), id2)
nodeChildren := parent.GetNodeChildren()
assert.Len(t, nodeChildren, 2)
delete(nodeChildren, id1)
parent.SetChildren(nodeChildren)
nodeChildren = parent.GetNodeChildren()
assert.Len(t, nodeChildren, 1)
}
func TestNodeID(t *testing.T) {
node := NewNode()
assert.EqualValues(t, NilID, node.GetID())
@ -128,7 +162,7 @@ type treeChildren struct {
Tree
}
func NewTreeChildren() TreeInterface {
func newTreeChildren() TreeInterface {
tree := &treeChildren{}
tree.Init(tree, newTestOptions())
treeDriver := &driverTreeChildren{}
@ -143,7 +177,7 @@ func NewTreeChildren() TreeInterface {
}
func TestNodeListPage(t *testing.T) {
tree := NewTreeChildren()
tree := newTreeChildren()
ctx := context.Background()
node := tree.Factory(ctx, KindNil)
@ -158,7 +192,7 @@ func TestNodeListPage(t *testing.T) {
}
func TestNodeList(t *testing.T) {
tree := NewTreeChildren()
tree := newTreeChildren()
ctx := context.Background()
node := tree.Factory(ctx, KindNil)

View file

@ -10,7 +10,6 @@ import (
)
type Path interface {
Clone() Path
Length() int
PathString() PathString
PathMappedString() PathString
@ -19,6 +18,7 @@ type Path interface {
RemoveFirst() Path
PopFirst() (NodeInterface, Path)
Pop() (NodeInterface, Path)
RemoveLast() Path
Empty() bool
First() NodeInterface
Last() NodeInterface
@ -29,16 +29,16 @@ type PathImplementation []NodeInterface
func PathAbsoluteString(current, destination string) string {
if !strings.HasPrefix(destination, "/") {
return current + "/" + destination
return filepath.Clean(current + "/" + destination)
}
return destination
return filepath.Clean(destination)
}
func PathAbsolute(current, destination string) Path {
return NewPathFromString(PathAbsoluteString(current, destination))
}
func PathStringRelative(current, destination string) string {
func PathRelativeString(current, destination string) string {
r, err := filepath.Rel(current, destination)
if err != nil {
panic(err)
@ -75,8 +75,14 @@ func NewPath(nodes ...NodeInterface) Path {
func (o PathImplementation) PathString() PathString {
elements := make([]string, 0, 10)
for _, node := range o {
elements = append(elements, string(node.GetID()))
for i, node := range o {
id := node.GetID()
element := string(id)
// i == 0 is root and intentionally empty
if i > 0 && id == NilID {
element = "nothing"
}
elements = append(elements, element)
}
return elements
}
@ -97,10 +103,6 @@ func (o PathImplementation) Length() int {
return len(o)
}
func (o PathImplementation) Clone() Path {
return o[:]
}
func (o PathImplementation) Append(child NodeInterface) Path {
return append(o, child)
}

View file

@ -104,3 +104,62 @@ func TestNodePath(t *testing.T) {
assert.EqualValues(t, "/1/2", pathString.Join())
}
}
func TestPathAbsoluteString(t *testing.T) {
assert.Equal(t, "/c", PathAbsoluteString("/a/b", "/c/d/.."))
assert.Equal(t, "/a/b/c", PathAbsoluteString("/a/b", "c"))
assert.Equal(t, "/a/b/c", PathAbsoluteString("/a/./b", "c/d/.."))
}
func TestPathAbsolute(t *testing.T) {
assert.Equal(t, "/a/b/c", PathAbsolute("/a/b", "c").String())
}
func TestPathRelativeString(t *testing.T) {
assert.Equal(t, "../c", PathRelativeString("/a/b/d", "/a/b/c"))
assert.Panics(t, func() { PathRelativeString("/a/b/d", "") })
}
func TestPathString(t *testing.T) {
path := NewPathFromString("/a/b/c")
assert.Equal(t, "/a/b/c", path.PathString().Join())
last, path := path.Pop()
path = path.Append(NewNode())
path = path.Append(last)
assert.Equal(t, "/a/b/nothing/c", path.PathString().Join())
}
func TestPathMappedString(t *testing.T) {
path := NewPathFromString("/a/b")
for _, node := range path.All() {
node.SetDriver(NewNullDriver())
node.SetMappedID(node.GetID() + "M")
}
assert.Equal(t, "M/aM/bM", path.PathMappedString().Join())
}
func TestPathMethods(t *testing.T) {
path := NewPathFromString("/a/b/c")
assert.Equal(t, "/a/b/c", path.String())
assert.Equal(t, 4, path.Length())
assert.Len(t, path.All(), 4)
assert.False(t, path.Empty())
first, path := path.PopFirst()
assert.Equal(t, "", string(first.GetID()))
assert.Equal(t, "a/b/c", path.String())
assert.Equal(t, "a", string(path.First().GetID()))
assert.Equal(t, "c", string(path.Last().GetID()))
firstRemoved := path.RemoveFirst()
assert.Equal(t, "b/c", firstRemoved.String())
last, lastRemoved := path.Pop()
assert.Equal(t, "c", string(last.GetID()))
assert.Equal(t, "a/b", lastRemoved.String())
lastRemoved = path.RemoveLast()
assert.Equal(t, "a/b", lastRemoved.String())
}

View file

@ -6,10 +6,49 @@ package generic
import (
"context"
"strings"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/util"
)
type ErrorRemapReferencesRelative error
func RemapReferences(ctx context.Context, node NodeInterface, f f3.Interface) {
for _, reference := range f.GetReferences() {
toPath := NewPath()
collectTo := func(ctx context.Context, parent, path Path, node NodeInterface) {
element := NewNode()
mappedID := node.GetMappedID()
if mappedID == "" {
node.Trace("mapped ID for %s is not defined", path)
}
element.SetID(mappedID)
toPath = toPath.Append(element)
}
from := reference.Get()
isRelative := !strings.HasPrefix(from, "/")
if isRelative && !strings.HasPrefix(from, "..") {
panic(NewError[ErrorRemapReferencesRelative]("relative references that do not start with .. are not supported %s", from))
}
current := node.GetCurrentPath().String()
fromPath := PathAbsolute(current, from)
node.GetTree().Apply(ctx, fromPath, NewApplyOptions(collectTo).SetWhere(ApplyEachNode))
to := toPath.String()
node.Trace("from '%s' to '%s'", fromPath, to)
if isRelative {
currentMapped := node.GetParent().GetCurrentPath().PathMappedString().Join()
// because the mapped ID of the current node has not been allocated yet
// and it does not matter as long as it is replaced with ..
// it will not work at all if a relative reference does not start with ..
currentMapped += "/PLACEHODLER"
to = PathRelativeString(currentMapped, to)
}
node.Trace("convert reference %s => %s", reference.Get(), to)
reference.Set(to)
}
}
func NodeCollectReferences(ctx context.Context, node NodeInterface) []Path {
pathToReferences := make(map[string]Path, 5)
@ -23,15 +62,9 @@ func NodeCollectReferences(ctx context.Context, node NodeInterface) []Path {
if _, ok := pathToReferences[absoluteReference]; ok {
continue
}
found := tree.ApplyAndGet(ctx, NewPathFromString(absoluteReference), NewApplyOptions(func(ctx context.Context, parent, path Path, node NodeInterface) {
if !node.GetIsSync() {
node.Get(ctx)
}
tree.ApplyAndGet(ctx, NewPathFromString(absoluteReference), NewApplyOptions(func(ctx context.Context, parent, path Path, node NodeInterface) {
pathToReferences[absoluteReference] = node.GetCurrentPath()
}))
if !found {
panic(NewError[ErrorNodeNotFound]("reference %s is not found in %s", reference.Get(), node.GetCurrentPath()))
}
}
}

View file

@ -0,0 +1,163 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"testing"
"code.forgejo.org/f3/gof3/v3/f3"
"github.com/stretchr/testify/assert"
)
type referenceFormat struct {
f3.Common
R *f3.Reference
}
func (o *referenceFormat) Clone() f3.Interface {
clone := &referenceFormat{}
*clone = *o
return clone
}
func (o *referenceFormat) GetReferences() f3.References {
references := o.Common.GetReferences()
if o.R != nil {
references = append(references, o.R)
}
return references
}
type referencesNodeDriver struct {
NullDriver
f referenceFormat
}
func (o *referencesNodeDriver) GetIDFromName(ctx context.Context, name string) NodeID {
return NilID
}
func (o *referencesNodeDriver) Get(context.Context) bool {
return true
}
func (o *referencesNodeDriver) NewFormat() f3.Interface {
return &referenceFormat{}
}
func (o *referencesNodeDriver) ToFormat() f3.Interface {
return &o.f
}
func (o *referencesNodeDriver) FromFormat(f f3.Interface) {
o.f = *f.(*referenceFormat)
}
func newReferencesNodeDriver() NodeDriverInterface {
return &referencesNodeDriver{}
}
type testTreeReferencesDriver struct {
NullTreeDriver
}
func newTestTreeReferencesDriver() TreeDriverInterface {
return &testTreeReferencesDriver{}
}
func (o *testTreeReferencesDriver) Factory(ctx context.Context, kind Kind) NodeDriverInterface {
d := newReferencesNodeDriver()
d.SetTreeDriver(o)
return d
}
var kindTestNodeReferences = Kind("references")
type testNodeReferences struct {
testNode
}
func newTestTreeReferences() TreeInterface {
tree := &testTree{}
tree.Init(tree, newTestOptions())
tree.SetDriver(newTestTreeReferencesDriver())
tree.Register(kindTestNodeReferences, func(ctx context.Context, kind Kind) NodeInterface {
node := &testNodeReferences{}
return node.Init(node)
})
return tree
}
func TestTreeCollectReferences(t *testing.T) {
tree := newTestTreeReferences()
root := tree.Factory(context.Background(), kindTestNodeReferences)
tree.SetRoot(root)
one := tree.Factory(context.Background(), kindTestNodeReferences)
one.FromFormat(&referenceFormat{R: f3.NewReference("/somewhere")})
one.SetID("one")
root.SetChild(one)
// the second node that has the same reference is here to ensure
// they are deduplicated
two := tree.Factory(context.Background(), kindTestNodeReferences)
two.FromFormat(&referenceFormat{R: f3.NewReference("/somewhere")})
two.SetID("two")
root.SetChild(two)
references := TreeCollectReferences(context.Background(), tree, NewPathFromString("/"))
assert.Len(t, references, 1)
assert.EqualValues(t, "/somewhere", references[0].String())
}
func TestRemapReferences(t *testing.T) {
tree := newTestTreeReferences()
root := tree.Factory(context.Background(), kindTestNodeReferences)
tree.SetRoot(root)
one := tree.Factory(context.Background(), kindTestNodeReferences)
one.SetID("one")
one.SetMappedID("remappedone")
one.SetParent(root)
root.SetChild(one)
two := tree.Factory(context.Background(), kindTestNodeReferences)
two.FromFormat(&referenceFormat{R: f3.NewReference("/one")})
two.SetID("two")
two.SetMappedID("two")
two.SetParent(root)
root.SetChild(two)
{
f := two.ToFormat()
RemapReferences(context.Background(), two, f)
r := f.GetReferences()
if assert.Len(t, r, 1) {
assert.Equal(t, "/remappedone", r[0].Get())
}
}
three := tree.Factory(context.Background(), kindTestNodeReferences)
three.FromFormat(&referenceFormat{R: f3.NewReference("../../one")})
three.SetID("three")
three.SetMappedID("three")
three.SetParent(two)
two.SetChild(three)
{
f := three.ToFormat()
RemapReferences(context.Background(), three, f)
r := f.GetReferences()
if assert.Len(t, r, 1) {
assert.Equal(t, "../../remappedone", r[0].Get())
}
}
assert.Panics(t, func() { RemapReferences(context.Background(), three, &referenceFormat{R: f3.NewReference("./one")}) })
}

View file

@ -9,6 +9,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTreeInit(t *testing.T) {
@ -40,17 +41,48 @@ func TestTreePageSize(t *testing.T) {
assert.EqualValues(t, pageSize, tree.GetPageSize())
}
func TestTreeAllocateID(t *testing.T) {
tree := NewTree(newTestOptions())
assert.True(t, tree.AllocateID())
}
func TestTreeFactoryDerived(t *testing.T) {
tree := NewTestTree()
root := tree.Factory(context.Background(), KindTestNodeLevelOne)
tree := newTestTree()
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
tree.SetRoot(root)
assert.True(t, root == tree.GetRoot())
assert.True(t, KindTestNodeLevelOne == root.GetKind())
assert.True(t, kindTestNodeLevelOne == root.GetKind())
assert.True(t, tree.GetSelf() == root.GetTree().GetSelf())
leveltwo := tree.Factory(context.Background(), KindTestNodeLevelTwo)
leveltwo := tree.Factory(context.Background(), kindTestNodeLevelTwo)
leveltwo.SetParent(root)
assert.True(t, root == leveltwo.GetParent())
assert.True(t, KindTestNodeLevelTwo == leveltwo.GetKind())
assert.True(t, kindTestNodeLevelTwo == leveltwo.GetKind())
assert.True(t, tree.GetSelf() == leveltwo.GetTree().GetSelf())
}
func TestTreeApply(t *testing.T) {
tree := newTestTree()
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
tree.SetRoot(root)
assert.True(t, tree.Apply(context.Background(), NewPath(), nil))
assert.True(t, tree.Apply(context.Background(), NewPathFromString("/"),
NewApplyOptions(func(ctx context.Context, parent, path Path, node NodeInterface) {
require.Equal(t, root, node)
})),
)
}
func TestTreeApplyAndGet(t *testing.T) {
tree := newTestTree()
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
tree.SetRoot(root)
assert.True(t, tree.ApplyAndGet(context.Background(), NewPath(), nil))
assert.True(t, tree.ApplyAndGet(context.Background(), NewPathFromString("/"),
NewApplyOptions(func(ctx context.Context, parent, path Path, node NodeInterface) {
require.Equal(t, root, node)
})),
)
}

View file

@ -6,10 +6,7 @@ package generic
import (
"context"
"fmt"
"strings"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/util"
)
@ -91,37 +88,6 @@ func TreeUnify(ctx context.Context, origin, destination TreeInterface, options *
NodeUnify(ctx, originRoot, NewPath(), destinationRoot, NewPath(), options)
}
func RemapReferences(ctx context.Context, node NodeInterface, f f3.Interface) {
for _, reference := range f.GetReferences() {
toPath := NewPath()
collectTo := func(ctx context.Context, parent, path Path, node NodeInterface) {
element := NewNode()
element.SetID(node.GetMappedID())
toPath = toPath.Append(element)
}
from := reference.Get()
isRelative := !strings.HasPrefix(from, "/")
if isRelative && !strings.HasPrefix(from, "..") {
panic(fmt.Errorf("relative references that do not start with .. are not supported %s", from))
}
current := node.GetCurrentPath().String()
fromPath := PathAbsolute(current, from)
node.Trace("collectTo %s", from)
node.GetTree().Apply(ctx, fromPath, NewApplyOptions(collectTo).SetWhere(ApplyEachNode))
to := toPath.String()
if isRelative {
currentMapped := node.GetParent().GetCurrentPath().PathMappedString().Join()
// because the mapped ID of the current node has not been allocated yet
// and it does not matter as long as it is replaced with ..
// it will not work at all if a relative reference does not start with ..
currentMapped += "/PLACEHODLER"
to = PathStringRelative(currentMapped, to)
}
node.Trace("convert reference %s => %s", reference.Get(), to)
reference.Set(to)
}
}
func NodeCopy(ctx context.Context, origin, destination NodeInterface, destinationID NodeID, options *UnifyOptions) {
f := origin.GetSelf().ToFormat()
if options.noremap {

View file

@ -14,8 +14,8 @@ import (
func TestUnifyNilRootOrigin(t *testing.T) {
ctx := context.Background()
origin := NewTestTree()
destination := NewTestTree()
origin := newTestTree()
destination := newTestTree()
destinationRoot := NewNode()
destination.SetRoot(destinationRoot)
assert.Equal(t, destinationRoot, destination.GetRoot())
@ -26,10 +26,10 @@ func TestUnifyNilRootOrigin(t *testing.T) {
func TestUnifyNilRootDestination(t *testing.T) {
ctx := context.Background()
origin := NewTestTree()
originRoot := origin.Factory(ctx, KindTestNodeLevelOne)
origin := newTestTree()
originRoot := origin.Factory(ctx, kindTestNodeLevelOne)
origin.SetRoot(originRoot)
destination := NewTestTree()
destination := newTestTree()
TreeUnify(ctx, origin, destination, NewUnifyOptions(destination))
assert.EqualValues(t, KindTestNodeLevelOne, destination.GetRoot().GetKind())
assert.EqualValues(t, kindTestNodeLevelOne, destination.GetRoot().GetKind())
}

View file

@ -179,7 +179,6 @@ func (f *Creator) GenerateMilestone() *f3.Milestone {
func (f *Creator) GeneratePullRequest(parent generic.Path) *f3.PullRequest {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last())
project := f3_tree.GetFirstFormat[*f3.Project](parent.Last())
projectNode := f3_tree.GetFirstNodeKind(parent.Last(), f3_tree.KindProject)
repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
repositoryNode.Get(context.Background())
@ -210,16 +209,14 @@ func (f *Creator) GeneratePullRequest(parent generic.Path) *f3.PullRequest {
MergedTime: nil,
MergeCommitSHA: "",
Head: f3.PullRequestBranch{
Ref: featureRef,
SHA: featureSha,
RepoName: project.Name,
OwnerName: user.UserName,
Ref: featureRef,
SHA: featureSha,
Repository: f3_tree.NewPullRequestSameRepositoryReference(),
},
Base: f3.PullRequestBranch{
Ref: mainRef,
SHA: mainSha,
RepoName: project.Name,
OwnerName: user.UserName,
Ref: mainRef,
SHA: mainSha,
Repository: f3_tree.NewPullRequestSameRepositoryReference(),
},
}
}

View file

@ -70,7 +70,7 @@ func TestF3Mirror(t *testing.T) {
fixtureTree.Trace("======= compare fixture with forge mirrored to filesystem")
for _, pathPair := range pathPairs {
assert.True(t, generic.TreeCompare(ctx, fixtureTree, pathPair[0], verificationTree, pathPair[1], generic.NewCompareOptions()))
assert.True(t, generic.TreeCompare(ctx, fixtureTree, pathPair[0], verificationTree, pathPair[1]))
}
TreeDelete(t, testForge.GetNonTestUsers(), forgeWriteOptions, forgeWriteTree)
@ -175,7 +175,7 @@ func TestF3Tree(t *testing.T) {
otherTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
otherTree.GetOptions().(options.URLInterface).SetURL(tree.GetOptions().(options.URLInterface).GetURL())
otherTree.WalkAndGet(ctx, generic.NewWalkOptions(nil))
assert.True(t, generic.TreeCompare(ctx, tree, generic.NewPathFromString(""), otherTree, generic.NewPathFromString(""), generic.NewCompareOptions()))
assert.True(t, generic.TreeCompare(ctx, tree, generic.NewPathFromString(""), otherTree, generic.NewPathFromString("")))
})
}
}

View file

@ -50,7 +50,7 @@ func TestF3FilesystemMappedID(t *testing.T) {
generic.TreeMirror(ctx, aTree, bTree, rootPath, generic.NewMirrorOptions())
assert.True(t, generic.TreeCompare(ctx, aTree, rootPath, bTree, rootPath, generic.NewCompareOptions()))
assert.True(t, generic.TreeCompare(ctx, aTree, rootPath, bTree, rootPath))
//
// aTree maps user id 10111 to 10111.mapped

View file

@ -78,7 +78,7 @@ func ComplianceTests(t *testing.T, tree f3_tree.TreeInterface, kindToFixturePath
tree.Trace("about to insert child %s", id)
assert.EqualValues(t, generic.NilNode, parent.GetChild(child.GetID()))
} else {
tree.Trace("about insert child with nil ID")
tree.Trace("about to insert child with nil ID")
}
if child.GetDriver().IsNull() {
t.Skip("no driver, skipping")
@ -107,7 +107,7 @@ func ComplianceTests(t *testing.T, tree f3_tree.TreeInterface, kindToFixturePath
}
child.FromFormat(a)
b := child.ToFormat()
cmpOptions := f3.NewCmpCompareOptions(a)
cmpOptions := f3.NewCmpOptions(a)
require.True(t, cmp.Equal(a, b, cmpOptions), cmp.Diff(a, b, cmpOptions))
child.FromFormat(saved)

View file

@ -318,7 +318,6 @@ func GeneratorModifyReleaseAsset(asset *f3.ReleaseAsset, parent generic.Path) *f
func GeneratorSetRandomPullRequest(t *testing.T, pullRequest *f3.PullRequest, parent generic.Path) *f3.PullRequest {
user := f3_tree.GetFirstFormat[*f3.User](parent.Last())
project := f3_tree.GetFirstFormat[*f3.Project](parent.Last())
projectNode := f3_tree.GetFirstNodeKind(parent.Last(), f3_tree.KindProject)
repositoryNode := projectNode.Find(generic.NewPathFromString("repositories/vcs"))
repositoryNode.Get(context.Background())
@ -348,16 +347,14 @@ func GeneratorSetRandomPullRequest(t *testing.T, pullRequest *f3.PullRequest, pa
pullRequest.MergedTime = nil
pullRequest.MergeCommitSHA = ""
pullRequest.Head = f3.PullRequestBranch{
Ref: featureRef,
SHA: featureSha,
RepoName: project.Name,
OwnerName: user.UserName,
Ref: featureRef,
SHA: featureSha,
Repository: f3.NewReference("../../repository/vcs"),
}
pullRequest.Base = f3.PullRequestBranch{
Ref: mainRef,
SHA: mainSha,
RepoName: project.Name,
OwnerName: user.UserName,
Ref: mainRef,
SHA: mainSha,
Repository: f3.NewReference("../../repository/vcs"),
}
return pullRequest
}

View file

@ -60,7 +60,8 @@ func TestF3PullRequest(t *testing.T) {
fixtureTree.Trace("======= compare fixture with forge mirrored to filesystem")
for _, pathPair := range pathPairs {
assert.True(t, generic.TreeCompare(ctx, fixtureTree, pathPair[0], verificationTree, pathPair[1], generic.NewCompareOptions()))
fixtureTree.Trace("======= compare %s with %s", pathPair[0], pathPair[1])
assert.True(t, generic.TreeCompare(ctx, fixtureTree, pathPair[0], verificationTree, pathPair[1]))
}
TreeDelete(t, testForge.GetNonTestUsers(), forgeWriteOptions, forgeWriteTree)

View file

@ -22,16 +22,16 @@ func TestCompare(t *testing.T) {
bTree := NewMemoryTree(ctx, "O")
testTreeBuild(t, bTree, 2)
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString(""), generic.NewCompareOptions()))
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
{
toDelete := generic.NewPathFromString("/O-A/O-B")
aTree.Find(toDelete).Delete(ctx)
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString(""), generic.NewCompareOptions()))
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
bTree.Find(toDelete).Delete(ctx)
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString(""), generic.NewCompareOptions()))
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
}
{
@ -40,33 +40,33 @@ func TestCompare(t *testing.T) {
aNode := aTree.Find(toModify)
memory.SetContent(aNode, content)
aNode.Upsert(ctx)
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString(""), generic.NewCompareOptions()))
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
bNode := bTree.Find(toModify)
memory.SetContent(bNode, content)
bNode.Upsert(ctx)
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString(""), generic.NewCompareOptions()))
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
}
{
toModify := generic.NewPathFromString("/O-A/O-F")
aTree.Find(toModify).SetKind(generic.Kind("???"))
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString(""), generic.NewCompareOptions()))
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
bTree.Find(toModify).SetKind(generic.Kind("???"))
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString(""), generic.NewCompareOptions()))
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
}
{
pathToMap := generic.NewPathFromString("/O-A/O-J/O-M")
mappedID := generic.NodeID("MAPPED")
aTree.Find(pathToMap).SetMappedID(mappedID)
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString(""), generic.NewCompareOptions()))
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
bNode := bTree.Find(pathToMap).Delete(ctx)
bNode.SetID(mappedID)
parentPathToMap := generic.NewPathFromString("/O-A/O-J")
bTree.Find(parentPathToMap).SetChild(bNode)
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString(""), generic.NewCompareOptions()))
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
}
}

View file

@ -21,7 +21,3 @@ func RandSeq(n int) string {
}
return string(b)
}
func RandInt64() int64 {
return random.Int63()
}

16
util/rand_test.go Normal file
View file

@ -0,0 +1,16 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package util
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRandSeq(t *testing.T) {
l := 23
assert.Len(t, RandSeq(l), l)
}

View file

@ -1,44 +0,0 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package uri
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
)
// ErrURISchemeNotSupported represents a scheme error
type ErrURISchemeNotSupported struct {
Scheme string
}
func (e ErrURISchemeNotSupported) Error() string {
return fmt.Sprintf("Unsupported scheme: %v", e.Scheme)
}
// Open open a local file or a remote file
func Open(uriStr string) (io.ReadCloser, error) {
u, err := url.Parse(uriStr)
if err != nil {
return nil, err
}
switch strings.ToLower(u.Scheme) {
case "http", "https":
f, err := http.Get(uriStr)
if err != nil {
return nil, err
}
return f.Body, nil
case "file":
return os.Open(u.Path)
default:
return nil, ErrURISchemeNotSupported{Scheme: u.Scheme}
}
}

View file

@ -1,21 +0,0 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package uri
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestReadURI(t *testing.T) {
p, err := filepath.Abs("./uri.go")
assert.NoError(t, err)
f, err := Open("file://" + p)
assert.NoError(t, err)
defer f.Close()
}