Adding upstream version 3.10.8.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
37e9b6d587
commit
03bfe4079e
356 changed files with 28857 additions and 0 deletions
84
.deadcode-out
Normal file
84
.deadcode-out
Normal file
|
@ -0,0 +1,84 @@
|
|||
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
|
||||
|
||||
code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk
|
||||
hasAgent
|
||||
GetAgent
|
||||
Version
|
||||
NewClientWithHTTP
|
||||
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/gitlab
|
||||
common.getTree
|
||||
common.getF3Tree
|
||||
common.getChildDriver
|
||||
common.isContainer
|
||||
common.getURL
|
||||
common.getPushURL
|
||||
common.getNewMigrationHTTPClient
|
||||
common.getIsAdmin
|
||||
common.getVersion
|
||||
treeDriver.maybeSudo
|
||||
|
||||
code.forgejo.org/f3/gof3/v3/forges/helpers/tests/repository
|
||||
TestHelper.GetNode
|
||||
TestHelper.RevList
|
||||
TestHelper.AssertRepositoryNotFileExists
|
||||
TestHelper.BranchRepositoryFeature
|
||||
|
||||
code.forgejo.org/f3/gof3/v3/internal/hoverfly
|
||||
MainTest
|
||||
GetSingleton
|
||||
testSimulate.Run
|
||||
|
||||
code.forgejo.org/f3/gof3/v3/options/cli
|
||||
OptionsCLI.FromFlags
|
||||
OptionsCLI.GetFlags
|
||||
|
||||
code.forgejo.org/f3/gof3/v3/tree/f3
|
||||
NewLabelReference
|
||||
NewPullRequestLabelReference
|
||||
NewMilestoneReference
|
||||
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
|
||||
|
10
.editorconfig
Normal file
10
.editorconfig
Normal file
|
@ -0,0 +1,10 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
35
.forgejo/workflows/release.yml
Normal file
35
.forgejo/workflows/release.yml
Normal file
|
@ -0,0 +1,35 @@
|
|||
on:
|
||||
push:
|
||||
tags: 'v*'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: docker-bookworm
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/ci:1'
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
- run: |
|
||||
make f3-cli
|
||||
mkdir release
|
||||
mv f3-cli release/
|
||||
|
||||
- name: publish release
|
||||
uses: https://data.forgejo.org/actions/forgejo-release@v2.6.0
|
||||
with:
|
||||
url: "https://code.forgejo.org"
|
||||
repo: "f3/gof3"
|
||||
direction: upload
|
||||
tag: "${{ github.ref_name }}"
|
||||
sha: "${{ github.sha }}"
|
||||
release-dir: release
|
||||
token: ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }}
|
||||
override: true
|
||||
verbose: ${{ vars.VERBOSE || "false" }}
|
||||
release-notes-assistant: true
|
||||
hide-archive-link: true
|
38
.forgejo/workflows/test-gitea.yml
Normal file
38
.forgejo/workflows/test-gitea.yml
Normal file
|
@ -0,0 +1,38 @@
|
|||
#
|
||||
# secrets.F3_READ_PRIVATE_MIRRORS_TOKEN
|
||||
# https://code.forgejo.org/forgejo-mirror scope read:repository
|
||||
#
|
||||
on:
|
||||
pull_request_target:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'wip-gitea'
|
||||
|
||||
jobs:
|
||||
compliance-gitea:
|
||||
runs-on: lxc-bookworm
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
- name: install jq make
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get -q install -qq -y jq make
|
||||
|
||||
- run: make deps-backend lint
|
||||
|
||||
- name: install zstd for actions/cache@v4
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get -q install -y -qq zstd
|
||||
|
||||
- name: run tests
|
||||
run: |
|
||||
./tests/run.sh prepare_container
|
||||
su forgejo -c "./tests/run.sh test_gitea ${{ secrets.F3_READ_PRIVATE_MIRRORS_TOKEN }}"
|
54
.forgejo/workflows/test.yml
Normal file
54
.forgejo/workflows/test.yml
Normal file
|
@ -0,0 +1,54 @@
|
|||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
compliance:
|
||||
runs-on: lxc-bookworm
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
- name: install jq make
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get -q install -qq -y jq make
|
||||
|
||||
- run: make deps-backend lint
|
||||
|
||||
- name: install hoverfly
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get -q install -qq -y unzip wget
|
||||
version=$(./tests/run.sh hoverfly_version)
|
||||
wget https://github.com/SpectoLabs/hoverfly/releases/download/v$version/hoverfly_bundle_linux_amd64.zip
|
||||
unzip hoverfly_bundle_linux_amd64.zip
|
||||
mv hoverfly hoverctl /usr/local/bin
|
||||
|
||||
- name: install zstd for actions/cache@v4
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get -q install -y -qq zstd
|
||||
|
||||
- name: get GitLab version
|
||||
id: gitlab
|
||||
run: |
|
||||
echo "version=$(./tests/run.sh gitlab_version)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: cache GitLab OCI image
|
||||
uses: https://data.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
/srv/forgejo-binaries/gitlab
|
||||
key: gitlab-${{ steps.gitlab.outputs.version }}
|
||||
|
||||
- name: run tests
|
||||
run: |
|
||||
./tests/run.sh prepare_container
|
||||
su forgejo -c "./tests/run.sh run"
|
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
*~
|
||||
f3-cli
|
||||
coverage.out
|
||||
coverage.html
|
||||
tests/*.out
|
||||
format/schemas/.gitignore
|
||||
.cur-deadcode-out
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
[submodule "tests/setup-forgejo"]
|
||||
path = tests/setup-forgejo
|
||||
url = https://code.forgejo.org/actions/setup-forgejo
|
||||
[submodule "tests/end-to-end"]
|
||||
path = tests/end-to-end
|
||||
url = https://code.forgejo.org/forgejo/end-to-end
|
81
.golangci.yml
Normal file
81
.golangci.yml
Normal file
|
@ -0,0 +1,81 @@
|
|||
#
|
||||
# Copied from https://github.com/go-gitea/gitea/blob/cc649f0cb338a085373fd85a8b71e315701cbdc1/.golangci.yml
|
||||
#
|
||||
linters:
|
||||
enable:
|
||||
- gosimple
|
||||
- typecheck
|
||||
- govet
|
||||
- errcheck
|
||||
- staticcheck
|
||||
#- unused # disabled because it gets it wrong with golangci-lint@v1.51.2 run & go 1.20.3
|
||||
- gofmt
|
||||
- misspell
|
||||
- gocritic
|
||||
- bidichk
|
||||
- ineffassign
|
||||
- revive
|
||||
- gofumpt
|
||||
- depguard
|
||||
- nakedret
|
||||
- unconvert
|
||||
- wastedassign
|
||||
- nolintlint
|
||||
- stylecheck
|
||||
enable-all: false
|
||||
disable-all: true
|
||||
fast: false
|
||||
|
||||
run:
|
||||
go: 1.22
|
||||
timeout: 10m
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- forges/forgejo/sdk
|
||||
|
||||
linters-settings:
|
||||
stylecheck:
|
||||
checks: ["all", "-ST1005", "-ST1003"]
|
||||
nakedret:
|
||||
max-func-lines: 0
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
- singleCaseSwitch # Every time this occurred in the code, there was no other way.
|
||||
revive:
|
||||
ignore-generated-header: false
|
||||
severity: warning
|
||||
confidence: 0.8
|
||||
errorCode: 1
|
||||
warningCode: 1
|
||||
rules:
|
||||
- name: blank-imports
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
- name: if-return
|
||||
- name: increment-decrement
|
||||
- name: var-naming
|
||||
- name: var-declaration
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: time-naming
|
||||
- name: unexported-return
|
||||
- name: indent-error-flow
|
||||
- name: errorf
|
||||
- name: duplicated-imports
|
||||
- name: modifies-value-receiver
|
||||
depguard:
|
||||
#list-type: denylist
|
||||
# Check the list against standard lib.
|
||||
#include-go-root: true
|
||||
rules:
|
||||
main:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: use os or io instead
|
20
LICENSE
Normal file
20
LICENSE
Normal file
|
@ -0,0 +1,20 @@
|
|||
Copyright Earl Warren <contact@earl-warren.org>
|
||||
Copyright Loïc Dachary <loic@dachary.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
75
Makefile
Normal file
75
Makefile
Normal file
|
@ -0,0 +1,75 @@
|
|||
GO ?= $(shell go env GOROOT)/bin/go
|
||||
|
||||
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.7.0 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 # renovate: datasource=go
|
||||
UNCOVER_PACKAGE ?= github.com/gregoryv/uncover/cmd/uncover@latest
|
||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0 # renovate: datasource=go
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.24.0 # renovate: datasource=go
|
||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.0 # renovate: datasource=go
|
||||
|
||||
SCHEMAS_VERSION ?= v3
|
||||
|
||||
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//')
|
||||
|
||||
LDFLAGS := $(LDFLAGS) -X "code.forgejo.org/f3/gof3/v3/cmd.Version=$(VERSION)"
|
||||
|
||||
EXECUTABLE := f3-cli
|
||||
|
||||
GO_DIRS = cmd f3 forges logger main options tree util
|
||||
GO_SOURCES = $(shell find $(GO_DIRS) -type f -name "*.go")
|
||||
|
||||
$(EXECUTABLE): $(GO_SOURCES)
|
||||
$(GO) build -tags 'netgo osusergo' -ldflags '-extldflags -static -s -w $(LDFLAGS)' -o $@ code.forgejo.org/f3/gof3/v3/main
|
||||
|
||||
.PHONY: deps-backend
|
||||
deps-backend:
|
||||
$(GO) mod download
|
||||
$(GO) install $(GOFUMPT_PACKAGE)
|
||||
$(GO) install $(GOLANGCI_LINT_PACKAGE)
|
||||
$(GO) install $(UNCOVER_PACKAGE)
|
||||
$(GO) install $(MISSPELL_PACKAGE)
|
||||
$(GO) install $(DEADCODE_PACKAGE)
|
||||
$(GO) install $(GOMOCK_PACKAGE)
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@if ! $(MAKE) lint-run ; then echo "Please run 'make lint-fix' and commit the result" ; exit 1 ; fi
|
||||
|
||||
.PHONY: lint-run
|
||||
lint-run: lint-schemas
|
||||
$(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-schemas
|
||||
lint-schemas:
|
||||
status=0 ; for schema in f3/schemas/*.json ; do if ! jq '.|empty' $$schema ; then status=1 ; echo $$schema error ; fi ; done ; exit $$status
|
||||
d=`mktemp -d` ; trap "rm -fr $$d" EXIT ; git clone --branch $(SCHEMAS_VERSION) --quiet https://code.forgejo.org/f3/f3-schemas $$d/schemas ; diff --exclude '.*' -ru $$d/schemas f3/schemas
|
||||
|
||||
.PHONY: lint-schemas-fix
|
||||
lint-schemas-fix:
|
||||
d=`mktemp -d` ; trap "rm -fr $$d" EXIT ; git clone --branch $(SCHEMAS_VERSION) --quiet https://code.forgejo.org/f3/f3-schemas $$d/schemas ; cp $$d/schemas/* f3/schemas
|
||||
|
||||
.PHONY: lint-fix
|
||||
lint-fix: lint-schemas-fix
|
||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) --fix
|
||||
$(GO) run $(DEADCODE_PACKAGE) $(DEADCODE_ARGS) > .deadcode-out
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
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)
|
226
README.md
Normal file
226
README.md
Normal file
|
@ -0,0 +1,226 @@
|
|||
## gof3
|
||||
|
||||
As a CLI or as a library, GoF3 provides a single operation: mirroring. The origin and destination are designated by the URL of a forge and a path to the resource. For instance, `mirror --from-type forgejo --from https://code.forgejo.org/forgejo/lxc-helpers --to-type F3 --to /some/directory` will mirror a project in a local directory using the F3 format.
|
||||
|
||||
## Building
|
||||
|
||||
* Install go >= v1.21
|
||||
* make f3-cli
|
||||
* ./f3-cli mirror -h
|
||||
|
||||
## Example
|
||||
|
||||
### To F3
|
||||
|
||||
Login to https://code.forgejo.org and obtain an application token with
|
||||
read permissions at https://code.forgejo.org/user/settings/applications.
|
||||
|
||||
```sh
|
||||
f3-cli mirror \
|
||||
--from-type forgejo --from-forgejo-url https://code.forgejo.org \
|
||||
--from-forgejo-token $codetoken \
|
||||
--from-path /forge/organizations/actions/projects/cascading-pr \
|
||||
--to-type filesystem --to-filesystem-directory /tmp/cascading-pr
|
||||
```
|
||||
|
||||
### From F3
|
||||
|
||||
Run a local Forgejo instance with `serials=1 tests/setup-forgejo.sh` and obtain
|
||||
an application token with:
|
||||
|
||||
```sh
|
||||
docker exec --user 1000 forgejo1 forgejo admin user generate-access-token -u root --raw --scopes 'all,sudo'
|
||||
```
|
||||
|
||||
Mirror issues
|
||||
|
||||
```sh
|
||||
f3-cli mirror \
|
||||
--from-type filesystem --from-filesystem-directory /tmp/cascading-pr \
|
||||
--from-path /forge/organizations/actions/projects/cascading-pr/issues \
|
||||
--to-type forgejo --to-forgejo-url http://0.0.0.0:3001 \
|
||||
--to-forgejo-token $localtoken
|
||||
```
|
||||
|
||||
Visit them at http://0.0.0.0:3001/actions/cascading-pr/issues
|
||||
|
||||
## Testing
|
||||
|
||||
### Requirements
|
||||
|
||||
The tests require a live GitLab instance as well as a live Forgejo instance and will use up to 16GB of RAM.
|
||||
|
||||
* Install docker
|
||||
* `./test/run.sh`
|
||||
|
||||
## License
|
||||
|
||||
This project is [MIT licensed](LICENSE).
|
||||
|
||||
## Architecture
|
||||
|
||||
[F3](https://f3.forgefriends.org/) is a hierarchy designed to be stored in a file system. It is represented in memory with the [tree/generic](tree/generic) abstract data structure that can be saved and loaded from disk by the [forges/filesystem](forges/filesystem) driver. Each forge (e.g. [forges/forgejo](forges/forgejo)) is supported by a driver that is responsible for the interactions of each resource (e.g `issues`, `asset`, etc.).
|
||||
|
||||
### Tree
|
||||
|
||||
[tree/f3](tree/f3) implements a [F3](https://f3.forgefriends.org/) hierarchy based on the [tree/generic](tree/generic) data structure. The [tree](tree/generic/tree.go) has a [logger](logger) for messages, [options](options) defining which forge it relates to and how and a pointer to the root [node](tree/generic/node.go) of the hierarchy (i.e. the `forge` F3 resource).
|
||||
|
||||
The node ([tree/generic/node.go](tree/generic/node.go)) has:
|
||||
|
||||
* a unique id (e.g. the numerical id of an `issue`)
|
||||
* a parent
|
||||
* chidren (e.g. `issues` children are `issues`, `issue` children are `comments` and `reactions`)
|
||||
* a kind that maps to a F3 resource (e.g. `issue`, etc.)
|
||||
* a driver for its concrete implementation for a given forge
|
||||
|
||||
It relies on a forge driver for the concrete implemenation of a F3 resource (issue, reaction, repository, etc.). For instance the `issues` driver for Forgejo is responsible for listing the existing issues and the `issue` driver is responsible for creating, updating or deleting a Forgejo issue.
|
||||
|
||||
### F3 archive
|
||||
|
||||
The [F3 JSON schemas](https://code.forgejo.org/f3/f3-schemas/-/tree/main) are copied in [f3/schemas](f3/schemas). Their internal representation and validation is found in a source file named after the resource (e.g. an `issue` represented by [f3/schemas/issue.json](f3/schemas/issue.json) is implemented by [f3/issue.go](f3/issue.go)).
|
||||
|
||||
When a F3 resource includes data external to the JSON file (i.e. a Git repository or an asset file), the internal representation has a function to copy the data to the destination given in argument. For instance:
|
||||
|
||||
* [f3/repository.go](f3/repository.go) `FetchFunc(destination)` will `git fetch --mirror` the repository to the `destination` directory.
|
||||
* [f3/releaseasset.go](f3/releaseasset.go) `DownloadFunc()` returns a `io.ReadCloser` that will be used by the caller to copy the asset to its destination.
|
||||
|
||||
### Options
|
||||
|
||||
The Forge options at [options/interface.go](options/interface.go) define the parameters given when a forge is created:
|
||||
|
||||
Each forge driver is responsible for registering the options (e.g. [Forgejo options](forges/forgejo/options/options.go)) and for registering a factory that will create these options (e.g. [Forgejo options registration](forgejo/main.go)). In addition to the options that are shared by all forges such as the logger, it may define additional options.
|
||||
|
||||
### Driver interface
|
||||
|
||||
For each [F3](https://f3.forgefriends.org/) resource, the driver is responsible for:
|
||||
|
||||
* copying the [f3](f3) argument to `FromFormat` to the forge
|
||||
* `ToFormat` reads from the forge and convert the data into an [f3/resources.go](f3/resources.go)
|
||||
|
||||
A driver must have a unique name (e.g. `forgejo`) and [register](forges/forgejo/main.go):
|
||||
|
||||
* an [options factory](options/factory.go)
|
||||
* a [forge factory](tree/f3/forge_factory.go)
|
||||
|
||||
#### Tree driver
|
||||
|
||||
The [tree driver](tree/generic/driver_tree.go) functions (e.g. [forges/forgejo/tree.go](forges/forgejo/tree.go)) specialize [NullTreeDriver](tree/generic/driver_tree.go).
|
||||
|
||||
* **Factory(ctx context.Context, kind generic.Kind) generic.NodeDriverInterface** creates a new node driver for a given [`Kind`](tree/f3/kind.go).
|
||||
* **GetPageSize() int** returns the default page size.
|
||||
|
||||
#### Node driver
|
||||
|
||||
The [node driver](tree/generic/driver_node.go) functions for [each `Kind`](tree/f3/kind.go) (e.g. `issues`, `issue`, etc.) specialize [NullNodeDriver](tree/generic/driver_node.go). The examples are given for the Forgejo [`issue`](forges/forgejo/issue.go) and [`issues`](forges/forgejo/issues.go) drivers, matching the REST API endpoint to the driver function.
|
||||
|
||||
* **ListPage(context.Context, page int) ChildrenSlice** returns children of the node paginated [GET /repos/{owner}/{repo}/issues](https://code.forgejo.org/api/swagger/#/issue/issueListIssues)
|
||||
* **Get(context.Context)** get the content of the resource (e.g. [GET /repos/{owner}/{repo}/issues/{index}](https://code.forgejo.org/api/swagger/#/issue/issueGetIssue))
|
||||
* **Put(context.Context) NodeID** create a new resource and return the identifier (e.g. [POST /repos/{owner}/{repo}/issues](https://code.forgejo.org/api/swagger/#/issue/issueCreateIssue))
|
||||
* **Patch(context.Context)** modify an existing resource (e.g. [PATCH /repos/{owner}/{repo}/issues/{index}](https://code.forgejo.org/api/swagger/#/issue/issueEditIssue))
|
||||
* **Delete(context.Context)** delete an existing resource (e.g. [DELETE /repos/{owner}/{repo}/issues/{index}](https://code.forgejo.org/api/swagger/#/issue/issueDelete))
|
||||
* **NewFormat() f3.Interface** create a new `issue` F3 object
|
||||
* **FromFormat(f3.Interface)** set the internal representation from the given F3 resource
|
||||
* **ToFormat() f3.Interface** convert the internal representation into the corresponding F3 resource. For instance the internal representation of an `issue` for the Forgejo driver is the `Issue` struct of the Forgejo SDK.
|
||||
|
||||
#### Options
|
||||
|
||||
The [options](options) created by the factory are expected to provide the [options interfaces](options/interface.go):
|
||||
|
||||
* Required
|
||||
* LoggerInterface
|
||||
* URLInterface
|
||||
* Optional
|
||||
* CLIInterface if additional CLI arguments specific to the forge are supported
|
||||
|
||||
For instance [forges/forgejo/options/options.go](forges/forgejo/options/options.go) is created by [forges/forgejo/options.go](forges/forgejo/options.go).
|
||||
|
||||
### Driver implementation
|
||||
|
||||
A driver for a forge must be self contained in a directory (e.g. [forges/forgejo](forges/forgejo)). Functions shared by multiple forges are grouped in the [forges/helpers](forges/helpers) directory and split into one directory per `Kind` (e.g. [forges/helpers/pullrequest](forges/helpers/pullrequest)).
|
||||
|
||||
* [options.go](forges/forgejo/options.go) defines the name of the forge in the Name variable (e.g. Name = "forgejo")
|
||||
* [options/options.go](forges/forgejo/options/options.go) defines the options specific to the forge and the corresponding CLI flags
|
||||
* [main.go](forges/forgejo/main.go) calls f3_tree.RegisterForgeFactory to create the forge given its name
|
||||
* [tree.go](forges/forgejo/tree.go) has the `Factory()` function that maps a node kind (`issue`, `reaction`, etc.) into an object that is capable of interacting with it (CRUD).
|
||||
* one file per `Kind` (e.g. [forges/forgejo/issues.go](forges/forgejo/issues.go)).
|
||||
|
||||
### Idempotency
|
||||
|
||||
Mirroring is idempotent: it will produce the same result if repeated multiple times. The drivers functions are not required to be idempotent.
|
||||
|
||||
* The `Put` function will only be called if the resource does not already exist.
|
||||
* The `Patch` and `Delete` functions will only be called if the resource exists.
|
||||
|
||||
### Identifiers mapping
|
||||
|
||||
When a forge (e.g. Forgejo) is mirrored on the filesystem, the identifiers are preserved verbatim (e.g. the `issue` identifier). When the filesystem is mirrored to a forge, the identifiers cannot always be preserved. For instance if an `issue` with the identifier 1234 is downloaded from Forgejo and created on another Forgejo instance, it will be allocated an identifier by the Forgejo instance. It cannot request to be given a specific identifier.
|
||||
|
||||
### References
|
||||
|
||||
A F3 resource may reference another F3 resource by a path. For instance the user that authored an issue is represented by `/forge/users/1234` where `1234` is the unique identifier of the user. The reference is relative to the forge. The mirroring of a forge to another is responsible for converting the references using the identifier mapping stored in the origin forge. For instance if `/forge/users/1234` stored in the filesystem is created in Forgejo as `/forge/users/58`, the `issue` stored in the filesystem with its authored as `/forge/users/1234` will be created in Forgejo to be authored by `/forge/users/58` instead.
|
||||
|
||||
### Logger
|
||||
|
||||
The [tree/generic](tree/generic) has a pointer to a logger implementing [logger.Interface](logger/interface.go) which is made available to the nodes and the drivers.
|
||||
|
||||
### Context
|
||||
|
||||
All functions except for setters and getters have a `context.Context` argument which is checked (using [util/terminate.go](util/terminate.go)) to not be `Done` before performing a long lasting operation (e.g. a REST API call or a call to the Git CLI). It is not used otherwise.
|
||||
|
||||
### Error model
|
||||
|
||||
When an error that cannot be recovered from happens, `panic` is called, otherwise an `Error` is logged.
|
||||
|
||||
### CLI
|
||||
|
||||
The CLI is in [cmd](cmd) and relies on [options](options) to figure out which options are to be implemented for each supported forge.
|
||||
|
||||
## Hacking
|
||||
|
||||
### Local tests
|
||||
|
||||
The forge instance is deleted before each run and left running for forensic analysis when the run completes.
|
||||
|
||||
```sh
|
||||
./tests/run.sh test_forgejo # http://0.0.0.0:3001 user root, password admin1234
|
||||
./tests/run.sh test_gitlab # http://0.0.0.0:8181 user root, password Wrobyak4
|
||||
./tests/run.sh test_gitea # http://0.0.0.0:3001 user root, password admin1234
|
||||
```
|
||||
|
||||
Restart a new forge with:
|
||||
|
||||
```sh
|
||||
./tests/run.sh run_forgejo # http://0.0.0.0:3001 user root, password admin1234
|
||||
./tests/run.sh run_gitlab # http://0.0.0.0:8181 user root, password Wrobyak4
|
||||
./tests/run.sh run_gitea # http://0.0.0.0:3001 user root, password admin1234
|
||||
```
|
||||
|
||||
The compliance test resources are deleted, except if the environment variable `GOF3_TEST_COMPLIANCE_CLEANUP=false`.
|
||||
|
||||
```sh
|
||||
GOF3_TEST_COMPLIANCE_CLEANUP=false GOF3_FORGEJO_HOST_PORT=0.0.0.0:3001 go test -run=TestF3Forge/forgejo -v code.forgejo.org/f3/gof3/...
|
||||
```
|
||||
|
||||
### Code coverage
|
||||
|
||||
```sh
|
||||
export SCRATCHDIR=/tmp/gof3
|
||||
./tests/run.sh # collect coverage for every test
|
||||
./tests/run.sh run_forgejo # update coverage for forgejo
|
||||
./tests/run.sh test_merge_coverage # merge coverage from every test
|
||||
go tool cover -func /tmp/gof3/merged.out # show coverage per function
|
||||
uncover /tmp/gof3/merged.out GeneratorSetReviewComment # show which lines of the GeneratorSetReviewComment function are not covered
|
||||
```
|
||||
|
||||
### F3 schemas
|
||||
|
||||
The JSON schemas come from [the f3-schemas repository](https://code.forgejo.org/f3/f3-schemas) and
|
||||
should be updated as follows:
|
||||
|
||||
```
|
||||
cd f3 ; rm -fr schemas ; git --work-tree schemas clone https://code.forgejo.org/f3/f3-schemas ; rm -fr f3-schemas schemas/.gitignore schemas/.forgejo
|
||||
```
|
||||
|
||||
## Funding
|
||||
|
||||
See the page dedicated to funding in the [F3 documentation](https://f3.forgefriends.org/funding.html)
|
24
api/api.go
Normal file
24
api/api.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
"code.forgejo.org/f3/gof3/v3/util"
|
||||
)
|
||||
|
||||
func TreeMirror(ctx context.Context, originTree, destinationTree generic.TreeInterface, p path.Path, options *generic.MirrorOptions) error {
|
||||
err := util.PanicToError(func() {
|
||||
generic.TreeMirror(ctx, originTree, destinationTree, p, options)
|
||||
})
|
||||
if err != nil {
|
||||
originTree.Error(err.Error())
|
||||
originTree.Debug(err.Stack())
|
||||
}
|
||||
return err
|
||||
}
|
49
cmd/cli.go
Normal file
49
cmd/cli.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
|
||||
"code.forgejo.org/f3/gof3/v3/options"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func ForgeTypeOption(direction string) string {
|
||||
return direction + "-type"
|
||||
}
|
||||
|
||||
func GetFlagsCommon(prefix, category string) []cli.Flag {
|
||||
flags := make([]cli.Flag, 0, 10)
|
||||
|
||||
forgeTypes := make([]string, 0, 10)
|
||||
for name := range options.GetFactories() {
|
||||
forgeTypes = append(forgeTypes, name)
|
||||
}
|
||||
sort.Strings(forgeTypes)
|
||||
values := &enumType{
|
||||
Enum: forgeTypes,
|
||||
Default: filesystem_options.Name,
|
||||
}
|
||||
|
||||
flags = append(flags, &cli.GenericFlag{
|
||||
Name: ForgeTypeOption(prefix),
|
||||
Usage: fmt.Sprintf("`TYPE` of the %s forge", prefix),
|
||||
Value: values,
|
||||
DefaultText: values.GetDefaultText(),
|
||||
Category: prefix,
|
||||
})
|
||||
|
||||
flags = append(flags, &cli.StringFlag{
|
||||
Name: BuildForgePrefix(prefix, "path"),
|
||||
Usage: "resource to mirror (e.g. /forge/users/myuser/projects/myproject)",
|
||||
Category: prefix,
|
||||
})
|
||||
|
||||
return flags
|
||||
}
|
48
cmd/enumtype.go
Normal file
48
cmd/enumtype.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type enumType struct {
|
||||
Enum []string
|
||||
Default string
|
||||
selected string
|
||||
}
|
||||
|
||||
func (o enumType) Join() string {
|
||||
return strings.Join(o.Enum, ",")
|
||||
}
|
||||
|
||||
func (o *enumType) Set(value string) error {
|
||||
for _, enum := range o.Enum {
|
||||
if strings.EqualFold(enum, value) {
|
||||
o.selected = value
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("%v", o.Allowed())
|
||||
}
|
||||
|
||||
func (o *enumType) Allowed() string {
|
||||
return fmt.Sprintf("allowed values are %s", o.Join())
|
||||
}
|
||||
|
||||
func (o *enumType) GetDefaultText() string {
|
||||
return fmt.Sprintf("%s, %s", o.Default, o.Allowed())
|
||||
}
|
||||
|
||||
func (o enumType) Get() any {
|
||||
return o.String()
|
||||
}
|
||||
|
||||
func (o enumType) String() string {
|
||||
if o.selected == "" {
|
||||
return o.Default
|
||||
}
|
||||
return o.selected
|
||||
}
|
49
cmd/main.go
Normal file
49
cmd/main.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var Version = "development"
|
||||
|
||||
func SetVerbosity(ctx context.Context, verbosity int) {
|
||||
l := logger.ContextGetLogger(ctx)
|
||||
switch verbosity {
|
||||
case 0:
|
||||
l.SetLevel(logger.Info)
|
||||
default:
|
||||
l.SetLevel(logger.Trace)
|
||||
}
|
||||
}
|
||||
|
||||
func NewApp() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "F3",
|
||||
Usage: "Friendly Forge Format",
|
||||
Description: `Friendly Forge Format`,
|
||||
Version: Version,
|
||||
Before: func(ctx context.Context, c *cli.Command) (context.Context, error) {
|
||||
SetVerbosity(ctx, c.Count("verbose"))
|
||||
return nil, nil
|
||||
},
|
||||
Commands: []*cli.Command{
|
||||
CreateCmdMirror(),
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Usage: "increase the verbosity level",
|
||||
},
|
||||
cli.VersionFlag,
|
||||
},
|
||||
EnableShellCompletion: true,
|
||||
}
|
||||
}
|
129
cmd/mirror.go
Normal file
129
cmd/mirror.go
Normal file
|
@ -0,0 +1,129 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
"code.forgejo.org/f3/gof3/v3/options"
|
||||
"code.forgejo.org/f3/gof3/v3/path"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
"code.forgejo.org/f3/gof3/v3/util"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
directionFrom = "from"
|
||||
flagFrom = "--" + directionFrom
|
||||
directionTo = "to"
|
||||
flagTo = "--" + directionTo
|
||||
)
|
||||
|
||||
func BuildForgePrefix(prefix, forge string) string {
|
||||
return prefix + "-" + forge
|
||||
}
|
||||
|
||||
func FlagsToTree(ctx context.Context, c *cli.Command, direction string) generic.TreeInterface {
|
||||
forgeType := c.String(ForgeTypeOption(direction))
|
||||
opts := options.GetFactory(forgeType)()
|
||||
opts.(options.LoggerInterface).SetLogger(logger.ContextGetLogger(ctx))
|
||||
if o, ok := opts.(options.CLIInterface); ok {
|
||||
o.FromFlags(ctx, c, BuildForgePrefix(direction, forgeType))
|
||||
} else {
|
||||
panic("not implemented")
|
||||
}
|
||||
return generic.GetFactory("f3")(ctx, opts)
|
||||
}
|
||||
|
||||
func CreateCmdMirror() *cli.Command {
|
||||
flags := make([]cli.Flag, 0, 10)
|
||||
for _, direction := range []string{"from", "to"} {
|
||||
flags = append(flags, GetFlagsCommon(direction, "common")...)
|
||||
for name, factory := range options.GetFactories() {
|
||||
if opts, ok := factory().(options.CLIInterface); ok {
|
||||
flags = append(flags, opts.GetFlags(BuildForgePrefix(direction, name), name)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flags = func(flags []cli.Flag) []cli.Flag {
|
||||
dedup := make([]cli.Flag, 0, 10)
|
||||
names := make(map[string]any, 10)
|
||||
flagLoop:
|
||||
for _, flag := range flags {
|
||||
for _, name := range flag.Names() {
|
||||
_, found := names[name]
|
||||
if found {
|
||||
continue flagLoop
|
||||
}
|
||||
}
|
||||
dedup = append(dedup, flag)
|
||||
for _, name := range flag.Names() {
|
||||
names[name] = nil
|
||||
}
|
||||
}
|
||||
return dedup
|
||||
}(flags)
|
||||
|
||||
return &cli.Command{
|
||||
Name: "mirror",
|
||||
Usage: "Mirror",
|
||||
Description: "Mirror",
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
return util.PanicToError(func() { runMirror(ctx, c) })
|
||||
},
|
||||
Flags: flags,
|
||||
}
|
||||
}
|
||||
|
||||
func runMirror(ctx context.Context, c *cli.Command) {
|
||||
from := FlagsToTree(ctx, c, directionFrom)
|
||||
to := FlagsToTree(ctx, c, directionTo)
|
||||
fromPathString := c.String(BuildForgePrefix(directionFrom, "path"))
|
||||
fromPath := generic.NewPathFromString(fromPathString)
|
||||
toPathString := c.String(BuildForgePrefix(directionTo, "path"))
|
||||
toPath := generic.NewPathFromString(toPathString)
|
||||
|
||||
log := from.GetLogger()
|
||||
|
||||
fromURL := "(unset)"
|
||||
if url, ok := from.GetOptions().(options.URLInterface); ok {
|
||||
fromURL = url.GetURL()
|
||||
}
|
||||
toURL := "(unset)"
|
||||
if url, ok := to.GetOptions().(options.URLInterface); ok {
|
||||
toURL = url.GetURL()
|
||||
}
|
||||
log.Info("mirror %s (%s at %s) to %s (%s at %s)",
|
||||
fromPath, c.String(ForgeTypeOption(directionFrom)), fromURL,
|
||||
toPath, c.String(ForgeTypeOption(directionTo)), toURL,
|
||||
)
|
||||
|
||||
log.Debug("read %s from %T", fromPath, from)
|
||||
var fromNode generic.NodeInterface
|
||||
fromNode = generic.NilNode
|
||||
walkAndGet := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
|
||||
node.WalkAndGet(ctx, parent, generic.NewWalkOptions(nil))
|
||||
fromNode = node
|
||||
}
|
||||
from.ApplyAndGet(ctx, fromPath, generic.NewApplyOptions(walkAndGet))
|
||||
if fromNode == generic.NilNode {
|
||||
panic(fmt.Errorf("from %s not found", fromPath))
|
||||
}
|
||||
from.Debug("copy %s from %T to %T", fromPath, from, to)
|
||||
|
||||
if toPathString == "" {
|
||||
generic.TreeMirror(ctx, from, to, fromPath, generic.NewMirrorOptions())
|
||||
} else {
|
||||
toNode := to.FindAndGet(ctx, toPath)
|
||||
if toNode == generic.NilNode {
|
||||
panic(fmt.Errorf("to %s not found", toPath))
|
||||
}
|
||||
generic.NodeMirror(ctx, fromNode, toNode, generic.NewMirrorOptions())
|
||||
}
|
||||
}
|
139
cmd/mirror_test.go
Normal file
139
cmd/mirror_test.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
|
||||
forgejo_options "code.forgejo.org/f3/gof3/v3/forges/forgejo/options"
|
||||
"code.forgejo.org/f3/gof3/v3/options"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
f3_tests "code.forgejo.org/f3/gof3/v3/tree/tests/f3"
|
||||
tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_CmdMirrorArguments(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
output, err := runApp(ctx, "f3", "mirror", "--from-type", "garbage")
|
||||
assert.ErrorContains(t, err, `allowed values are `)
|
||||
assert.Contains(t, output, "Incorrect Usage:")
|
||||
}
|
||||
|
||||
func Test_CmdMirrorIntegrationDefaultToPath(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
fixtureOptions := tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t)
|
||||
fixtureTree := generic.GetFactory("f3")(ctx, fixtureOptions)
|
||||
log := fixtureTree.GetLogger()
|
||||
log.Trace("======= build fixture")
|
||||
f3_tests.TreeBuild(t, "CmdMirrorDefault", fixtureOptions, fixtureTree)
|
||||
|
||||
log.Trace("======= create mirror")
|
||||
mirrorOptions := tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t)
|
||||
mirrorTree := generic.GetFactory("f3")(ctx, mirrorOptions)
|
||||
|
||||
p := "/forge/users/10111"
|
||||
log.Trace("======= mirror %s", p)
|
||||
output, err := runApp(ctx, "f3", "--verbose", "mirror",
|
||||
"--from-filesystem-directory", fixtureOptions.(options.URLInterface).GetURL(),
|
||||
"--from-path", p,
|
||||
"--to-filesystem-directory", mirrorOptions.(options.URLInterface).GetURL(),
|
||||
)
|
||||
log.Trace("======= assert")
|
||||
assert.NoError(t, err)
|
||||
require.Contains(t, output, fmt.Sprintf("mirror %s (filesystem", p))
|
||||
|
||||
mirrorTree.WalkAndGet(ctx, generic.NewWalkOptions(nil))
|
||||
found := mirrorTree.Find(generic.NewPathFromString(p))
|
||||
require.NotEqualValues(t, found, generic.NilNode)
|
||||
assert.EqualValues(t, p, found.GetCurrentPath().String())
|
||||
}
|
||||
|
||||
func Test_CmdMirrorIntegrationSpecificToPath(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
mirrorOptions := tests_forge.GetFactory(forgejo_options.Name)().NewOptions(t)
|
||||
mirrorTree := generic.GetFactory("f3")(ctx, mirrorOptions)
|
||||
|
||||
fixtureOptions := tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t)
|
||||
fixtureTree := generic.GetFactory("f3")(ctx, fixtureOptions)
|
||||
|
||||
log := fixtureTree.GetLogger()
|
||||
creator := f3_tests.NewCreator(t, "CmdMirrorSpecific", log)
|
||||
|
||||
log.Trace("======= build fixture")
|
||||
|
||||
var fromPath string
|
||||
{
|
||||
fixtureUserID := "userID01"
|
||||
fixtureProjectID := "projectID01"
|
||||
|
||||
userFormat := creator.GenerateUser()
|
||||
userFormat.SetID(fixtureUserID)
|
||||
users := fixtureTree.MustFind(generic.NewPathFromString("/forge/users"))
|
||||
user := users.CreateChild(ctx)
|
||||
user.FromFormat(userFormat)
|
||||
user.Upsert(ctx)
|
||||
require.EqualValues(t, user.GetID(), users.GetIDFromName(ctx, userFormat.UserName))
|
||||
|
||||
projectFormat := creator.GenerateProject()
|
||||
projectFormat.SetID(fixtureProjectID)
|
||||
projects := user.MustFind(generic.NewPathFromString("projects"))
|
||||
project := projects.CreateChild(ctx)
|
||||
project.FromFormat(projectFormat)
|
||||
project.Upsert(ctx)
|
||||
require.EqualValues(t, project.GetID(), projects.GetIDFromName(ctx, projectFormat.Name))
|
||||
|
||||
fromPath = fmt.Sprintf("/forge/users/%s/projects/%s", userFormat.UserName, projectFormat.Name)
|
||||
}
|
||||
|
||||
log.Trace("======= create mirror")
|
||||
|
||||
var toPath string
|
||||
var projects generic.NodeInterface
|
||||
{
|
||||
userFormat := creator.GenerateUser()
|
||||
users := mirrorTree.MustFind(generic.NewPathFromString("/forge/users"))
|
||||
user := users.CreateChild(ctx)
|
||||
user.FromFormat(userFormat)
|
||||
user.Upsert(ctx)
|
||||
require.EqualValues(t, user.GetID(), users.GetIDFromName(ctx, userFormat.UserName))
|
||||
|
||||
projectFormat := creator.GenerateProject()
|
||||
projects = user.MustFind(generic.NewPathFromString("projects"))
|
||||
project := projects.CreateChild(ctx)
|
||||
project.FromFormat(projectFormat)
|
||||
project.Upsert(ctx)
|
||||
require.EqualValues(t, project.GetID(), projects.GetIDFromName(ctx, projectFormat.Name))
|
||||
|
||||
toPath = fmt.Sprintf("/forge/users/%s/projects/%s", userFormat.UserName, projectFormat.Name)
|
||||
}
|
||||
|
||||
log.Trace("======= mirror %s", fromPath)
|
||||
output, err := runApp(ctx, "f3", "--verbose", "mirror",
|
||||
"--from-type", filesystem_options.Name,
|
||||
"--from-path", fromPath,
|
||||
"--from-filesystem-directory", fixtureOptions.(options.URLInterface).GetURL(),
|
||||
|
||||
"--to-type", forgejo_options.Name,
|
||||
"--to-path", toPath,
|
||||
"--to-forgejo-user", mirrorOptions.(options.AuthInterface).GetUsername(),
|
||||
"--to-forgejo-password", mirrorOptions.(options.AuthInterface).GetPassword(),
|
||||
"--to-forgejo-url", mirrorOptions.(options.URLInterface).GetURL(),
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
log.Trace("======= assert")
|
||||
require.Contains(t, output, fmt.Sprintf("mirror %s", fromPath))
|
||||
projects.List(ctx)
|
||||
require.NotEmpty(t, projects.GetChildren())
|
||||
log.Trace("======= project %s", projects.GetChildren()[0])
|
||||
}
|
33
cmd/signal.go
Normal file
33
cmd/signal.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func InstallSignals() (context.Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
signalChannel := make(chan os.Signal, 1)
|
||||
|
||||
signal.Notify(
|
||||
signalChannel,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
)
|
||||
select {
|
||||
case <-signalChannel:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
cancel()
|
||||
signal.Reset()
|
||||
}()
|
||||
|
||||
return ctx, cancel
|
||||
}
|
38
cmd/testhelpers.go
Normal file
38
cmd/testhelpers.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/logger"
|
||||
// allow forges to register
|
||||
_ "code.forgejo.org/f3/gof3/v3/forges/filesystem"
|
||||
_ "code.forgejo.org/f3/gof3/v3/forges/forgejo"
|
||||
)
|
||||
|
||||
func runApp(ctx context.Context, args ...string) (string, error) {
|
||||
l := logger.NewCaptureLogger()
|
||||
ctx = logger.ContextSetLogger(ctx, l)
|
||||
|
||||
app := NewApp()
|
||||
|
||||
app.Writer = l.GetBuffer()
|
||||
app.ErrWriter = l.GetBuffer()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println(l.String())
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
err := app.Run(ctx, args)
|
||||
|
||||
fmt.Println(l.String())
|
||||
|
||||
return l.String(), err
|
||||
}
|
20
f3/ci.go
Normal file
20
f3/ci.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// Copyright twenty-panda <twenty-panda@posteo.com>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
type CI struct {
|
||||
Common
|
||||
}
|
||||
|
||||
func (o CI) Equal(other CI) bool {
|
||||
return o.Common.Equal(other.Common)
|
||||
}
|
||||
|
||||
func (o *CI) Clone() Interface {
|
||||
clone := &CI{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
35
f3/comment.go
Normal file
35
f3/comment.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import "time"
|
||||
|
||||
type Comment struct {
|
||||
Common
|
||||
PosterID *Reference `json:"poster_id"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func (o *Comment) GetReferences() References {
|
||||
references := o.Common.GetReferences()
|
||||
if !o.PosterID.IsNil() {
|
||||
references = append(references, o.PosterID)
|
||||
}
|
||||
return references
|
||||
}
|
||||
|
||||
func (o Comment) Equal(other Comment) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
nilOrEqual(o.PosterID, other.PosterID) &&
|
||||
o.Content == other.Content
|
||||
}
|
||||
|
||||
func (o *Comment) Clone() Interface {
|
||||
clone := &Comment{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
37
f3/equal.go
Normal file
37
f3/equal.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright limiting-factor <limiting-factor@posteo.com>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type equalConstraint[T any] interface {
|
||||
Equal(T) bool
|
||||
*T
|
||||
}
|
||||
|
||||
func nilOrEqual[P any, T equalConstraint[P]](a, b T) bool {
|
||||
return (a == nil && b == nil) ||
|
||||
(a != nil && b != nil && a.Equal(*b))
|
||||
}
|
||||
|
||||
func arrayEqual[P any, T equalConstraint[P]](a, b []T) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(a); i++ {
|
||||
if !nilOrEqual(a[i], b[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func nilOrEqualTimeToDate(a, b *time.Time) bool {
|
||||
return (a == nil && b == nil) ||
|
||||
(a != nil && b != nil && a.Format(time.DateOnly) == b.Format(time.DateOnly))
|
||||
}
|
53
f3/equal_test.go
Normal file
53
f3/equal_test.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright limiting-factor <limiting-factor@posteo.com>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type s struct {
|
||||
v int
|
||||
}
|
||||
|
||||
func (o s) Equal(other s) bool {
|
||||
return o.v == other.v
|
||||
}
|
||||
|
||||
func TestNilOrEqual(t *testing.T) {
|
||||
s1 := &s{1}
|
||||
s2 := &s{1}
|
||||
s3 := &s{2}
|
||||
assert.True(t, nilOrEqual[s](nil, nil))
|
||||
assert.False(t, nilOrEqual(s1, nil))
|
||||
assert.False(t, nilOrEqual(nil, s2))
|
||||
assert.True(t, nilOrEqual(s1, s2))
|
||||
assert.False(t, nilOrEqual(s1, s3))
|
||||
}
|
||||
|
||||
func TestArrayEqual(t *testing.T) {
|
||||
s1 := []*s{{1}, {2}}
|
||||
s2 := []*s{{1}, {2}}
|
||||
s3 := []*s{{1}, {2}, {3}}
|
||||
assert.True(t, arrayEqual(s1, s2))
|
||||
assert.False(t, arrayEqual(s1, s3))
|
||||
}
|
||||
|
||||
func TestNilOrEqualTimeToDate(t *testing.T) {
|
||||
t1, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
||||
require.NoError(t, err)
|
||||
t2, err := time.Parse(time.RFC3339, "2006-01-02T00:01:00Z")
|
||||
require.NoError(t, err)
|
||||
t3, err := time.Parse(time.RFC3339, "2026-01-02T11:01:00Z")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, nilOrEqualTimeToDate(nil, nil))
|
||||
assert.False(t, nilOrEqualTimeToDate(&t1, nil))
|
||||
assert.False(t, nilOrEqualTimeToDate(nil, &t2))
|
||||
assert.True(t, nilOrEqualTimeToDate(&t1, &t2))
|
||||
assert.False(t, nilOrEqualTimeToDate(&t1, &t3))
|
||||
}
|
109
f3/file_format.go
Normal file
109
f3/file_format.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/santhosh-tekuri/jsonschema/v6"
|
||||
)
|
||||
|
||||
// Load project data from file, with optional validation
|
||||
func Load(filename string, data any, validation bool) error {
|
||||
bs, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if validation {
|
||||
err := validate(bs, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return unmarshal(bs, data)
|
||||
}
|
||||
|
||||
func Store(filename string, data any) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
bs, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.Write(bs); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.Write([]byte("\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshal(bs []byte, data any) error {
|
||||
return json.Unmarshal(bs, data)
|
||||
}
|
||||
|
||||
func getSchema(filename string) (*jsonschema.Schema, error) {
|
||||
c := jsonschema.NewCompiler()
|
||||
return c.Compile(filename)
|
||||
}
|
||||
|
||||
func validate(bs []byte, datatype any) error {
|
||||
var v any
|
||||
err := unmarshal(bs, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var schemaFilename string
|
||||
switch datatype := datatype.(type) {
|
||||
case *User:
|
||||
schemaFilename = "schemas/user.json"
|
||||
case *Organization:
|
||||
schemaFilename = "schemas/organization.json"
|
||||
case *Project:
|
||||
schemaFilename = "schemas/project.json"
|
||||
case *Topic:
|
||||
schemaFilename = "schemas/topic.json"
|
||||
case *Issue:
|
||||
schemaFilename = "schemas/issue.json"
|
||||
case *PullRequest:
|
||||
schemaFilename = "schemas/pullrequest.json"
|
||||
case *Label:
|
||||
schemaFilename = "schemas/label.json"
|
||||
case *Milestone:
|
||||
schemaFilename = "schemas/milestone.json"
|
||||
case *Release:
|
||||
schemaFilename = "schemas/release.json"
|
||||
case *ReleaseAsset:
|
||||
schemaFilename = "schemas/releaseasset.json"
|
||||
case *Comment:
|
||||
schemaFilename = "schemas/comment.json"
|
||||
case *Reaction:
|
||||
schemaFilename = "schemas/reaction.json"
|
||||
case *Repository:
|
||||
schemaFilename = "schemas/repository.json"
|
||||
case *Review:
|
||||
schemaFilename = "schemas/review.json"
|
||||
case *ReviewComment:
|
||||
schemaFilename = "schemas/reviewcomment.json"
|
||||
case *CI:
|
||||
schemaFilename = "schemas/ci.json"
|
||||
default:
|
||||
return fmt.Errorf("file_format:validate: %T does not have a schema that could be used for validation", datatype)
|
||||
}
|
||||
|
||||
sch, err := getSchema(schemaFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sch.Validate(v)
|
||||
}
|
166
f3/file_format_test.go
Normal file
166
f3/file_format_test.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/santhosh-tekuri/jsonschema/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStoreLoad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
type S struct {
|
||||
A int
|
||||
B string
|
||||
}
|
||||
original := S{A: 1, B: "B"}
|
||||
p := filepath.Join(tmpDir, "s.json")
|
||||
assert.NoError(t, Store(p, original))
|
||||
var loaded S
|
||||
assert.NoError(t, Load(p, &loaded, false))
|
||||
assert.EqualValues(t, original, loaded)
|
||||
}
|
||||
|
||||
func TestF3_CI(t *testing.T) {
|
||||
var ci CI
|
||||
err := Load("file_format_testdata/ci/good.json", &ci, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/ci/bad.json", &ci, true)
|
||||
assert.ErrorContains(t, err, "'/index': value must be one of")
|
||||
}
|
||||
|
||||
func TestF3_User(t *testing.T) {
|
||||
var user User
|
||||
err := Load("file_format_testdata/user/good.json", &user, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/user/bad.json", &user, true)
|
||||
assert.ErrorContains(t, err, "missing properties 'index'")
|
||||
}
|
||||
|
||||
func TestF3_Organization(t *testing.T) {
|
||||
var organization Organization
|
||||
err := Load("file_format_testdata/organization/good.json", &organization, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/organization/bad.json", &organization, true)
|
||||
assert.ErrorContains(t, err, "missing properties 'index'")
|
||||
}
|
||||
|
||||
func TestF3_Project(t *testing.T) {
|
||||
var project Project
|
||||
err := Load("file_format_testdata/project/good.json", &project, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/project/bad.json", &project, true)
|
||||
assert.ErrorContains(t, err, "'/stars': got string, want number")
|
||||
}
|
||||
|
||||
func TestF3_Issue(t *testing.T) {
|
||||
var issue Issue
|
||||
err := Load("file_format_testdata/issue/good.json", &issue, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/issue/bad.json", &issue, true)
|
||||
assert.ErrorContains(t, err, "missing property 'index'")
|
||||
}
|
||||
|
||||
func TestF3_PullRequest(t *testing.T) {
|
||||
var pullRequest PullRequest
|
||||
err := Load("file_format_testdata/pullrequest/good.json", &pullRequest, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/pullrequest/bad.json", &pullRequest, true)
|
||||
assert.ErrorContains(t, err, "missing properties 'index'")
|
||||
}
|
||||
|
||||
func TestF3_Release(t *testing.T) {
|
||||
var release Release
|
||||
err := Load("file_format_testdata/release/good.json", &release, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/release/bad.json", &release, true)
|
||||
assert.ErrorContains(t, err, "missing properties 'index'")
|
||||
}
|
||||
|
||||
func TestF3_ReleaseAsset(t *testing.T) {
|
||||
var releaseAsset ReleaseAsset
|
||||
err := Load("file_format_testdata/releaseasset/good.json", &releaseAsset, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/releaseasset/bad.json", &releaseAsset, true)
|
||||
assert.ErrorContains(t, err, "missing properties 'index'")
|
||||
}
|
||||
|
||||
func TestF3_Comment(t *testing.T) {
|
||||
var comment Comment
|
||||
err := Load("file_format_testdata/comment/good.json", &comment, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/comment/bad.json", &comment, true)
|
||||
assert.ErrorContains(t, err, "'/created': 'AAAAAAAAA' is not valid date-time")
|
||||
}
|
||||
|
||||
func TestF3_Label(t *testing.T) {
|
||||
var label Label
|
||||
err := Load("file_format_testdata/label/good.json", &label, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/label/bad.json", &label, true)
|
||||
assert.ErrorContains(t, err, "'/exclusive': got string, want boolean")
|
||||
}
|
||||
|
||||
func TestF3_Milestone(t *testing.T) {
|
||||
var milestone Milestone
|
||||
err := Load("file_format_testdata/milestone/good.json", &milestone, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/milestone/bad.json", &milestone, true)
|
||||
assert.ErrorContains(t, err, "missing properties 'index'")
|
||||
}
|
||||
|
||||
func TestF3_Review(t *testing.T) {
|
||||
var review Review
|
||||
err := Load("file_format_testdata/review/good.json", &review, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/review/bad.json", &review, true)
|
||||
assert.ErrorContains(t, err, "missing properties 'index'")
|
||||
}
|
||||
|
||||
func TestF3_Reaction(t *testing.T) {
|
||||
var reaction Reaction
|
||||
err := Load("file_format_testdata/reaction/good.json", &reaction, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/reaction/bad.json", &reaction, true)
|
||||
assert.ErrorContains(t, err, "missing properties 'index'")
|
||||
}
|
||||
|
||||
func TestF3_Repository(t *testing.T) {
|
||||
var repository Repository
|
||||
err := Load("file_format_testdata/repository/good.json", &repository, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/repository/bad.json", &repository, true)
|
||||
assert.ErrorContains(t, err, "missing property 'name'")
|
||||
}
|
||||
|
||||
func TestF3_ReviewComment(t *testing.T) {
|
||||
var reviewComment ReviewComment
|
||||
err := Load("file_format_testdata/reviewcomment/good.json", &reviewComment, true)
|
||||
assert.NoError(t, err)
|
||||
err = Load("file_format_testdata/reviewcomment/bad.json", &reviewComment, true)
|
||||
assert.ErrorContains(t, err, "missing properties 'index'")
|
||||
}
|
||||
|
||||
func TestF3_Topic(t *testing.T) {
|
||||
var topic Topic
|
||||
err := Load("file_format_testdata/topic.json", &topic, true)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestF3_ValidationFail(t *testing.T) {
|
||||
var issue Issue
|
||||
err := Load("file_format_testdata/issue/bad.json", &issue, true)
|
||||
if _, ok := err.(*jsonschema.ValidationError); ok {
|
||||
errors := strings.Split(err.(*jsonschema.ValidationError).GoString(), "\n")
|
||||
assert.Contains(t, errors[1], "missing property")
|
||||
} else {
|
||||
t.Fatalf("got: type %T with value %s, want: *jsonschema.ValidationError", err, err)
|
||||
}
|
||||
}
|
3
f3/file_format_testdata/ci/bad.json
Normal file
3
f3/file_format_testdata/ci/bad.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"index": "Unknown"
|
||||
}
|
3
f3/file_format_testdata/ci/good.json
Normal file
3
f3/file_format_testdata/ci/good.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"index": "Forgejo Actions"
|
||||
}
|
7
f3/file_format_testdata/comment/bad.json
Normal file
7
f3/file_format_testdata/comment/bad.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"index": "5",
|
||||
"poster_id": "1",
|
||||
"created": "AAAAAAAAA",
|
||||
"updated": "1986-04-12T23:20:50.52Z",
|
||||
"content": "comment_content_5"
|
||||
}
|
14
f3/file_format_testdata/comment/good.json
Normal file
14
f3/file_format_testdata/comment/good.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"index": "5",
|
||||
"poster_id": "/user/1",
|
||||
"created": "1985-04-12T23:20:50.52Z",
|
||||
"updated": "1986-04-12T23:20:50.52Z",
|
||||
"content": "comment_content_5",
|
||||
"reactions": [
|
||||
{
|
||||
"index": "8",
|
||||
"user_id": "/user/23",
|
||||
"content": "laugh"
|
||||
}
|
||||
]
|
||||
}
|
20
f3/file_format_testdata/issue/bad.json
Normal file
20
f3/file_format_testdata/issue/bad.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"poster_id": "/forge/users/1",
|
||||
"title": "title_a",
|
||||
"content": "content_a",
|
||||
"milestone": "../../milestones/23",
|
||||
"state": "closed",
|
||||
"is_locked": false,
|
||||
"created": "1985-04-12T23:20:50.52Z",
|
||||
"updated": "1986-04-12T23:20:50.52Z",
|
||||
"closed": null,
|
||||
"due": "1986-04-12",
|
||||
"labels": [
|
||||
"../../labels/435"
|
||||
],
|
||||
"reactions": null,
|
||||
"assignees": [
|
||||
"/forge/users/1",
|
||||
"/forge/users/2"
|
||||
]
|
||||
}
|
21
f3/file_format_testdata/issue/good.json
Normal file
21
f3/file_format_testdata/issue/good.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"index": "1",
|
||||
"poster_id": "/forge/users/1",
|
||||
"title": "title_a",
|
||||
"content": "content_a",
|
||||
"milestone": "../../milestones/23",
|
||||
"state": "closed",
|
||||
"is_locked": false,
|
||||
"created": "1985-04-12T23:20:50.52Z",
|
||||
"updated": "1986-04-12T23:20:50.52Z",
|
||||
"closed": "1986-04-12T23:20:50.52Z",
|
||||
"due": "1986-04-12",
|
||||
"labels": [
|
||||
"../../labels/435"
|
||||
],
|
||||
"reactions": [],
|
||||
"assignees": [
|
||||
"/forge/users/1",
|
||||
"/forge/users/2"
|
||||
]
|
||||
}
|
7
f3/file_format_testdata/label/bad.json
Normal file
7
f3/file_format_testdata/label/bad.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"index": "1",
|
||||
"name": "label1",
|
||||
"description": "label1 description",
|
||||
"color": "ffffff",
|
||||
"exclusive": "CCCCCCCC"
|
||||
}
|
7
f3/file_format_testdata/label/good.json
Normal file
7
f3/file_format_testdata/label/good.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"index": "1",
|
||||
"name": "label1",
|
||||
"description": "label1 description",
|
||||
"color": "ffffff",
|
||||
"exclusive": false
|
||||
}
|
2
f3/file_format_testdata/milestone/bad.json
Normal file
2
f3/file_format_testdata/milestone/bad.json
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
10
f3/file_format_testdata/milestone/good.json
Normal file
10
f3/file_format_testdata/milestone/good.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"index": "1",
|
||||
"title": "title_a",
|
||||
"description": "description_a",
|
||||
"deadline": "1988-04-12T23:20:50.52Z",
|
||||
"created": "1985-04-12T23:20:50.52Z",
|
||||
"updated": "1986-04-12T23:20:50.52Z",
|
||||
"closed": "1987-04-12T23:20:50.52Z",
|
||||
"state": "closed"
|
||||
}
|
2
f3/file_format_testdata/organization/bad.json
Normal file
2
f3/file_format_testdata/organization/bad.json
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
5
f3/file_format_testdata/organization/good.json
Normal file
5
f3/file_format_testdata/organization/good.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"index": "9",
|
||||
"name": "orgunique",
|
||||
"full_name": "Org Unique"
|
||||
}
|
28
f3/file_format_testdata/project/bad.json
Normal file
28
f3/file_format_testdata/project/bad.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"index": "1",
|
||||
"name": "projectname",
|
||||
"is_private": false,
|
||||
"is_mirror": false,
|
||||
"description": "project description",
|
||||
"default_branch": "main",
|
||||
"repositories": [
|
||||
{
|
||||
"name": "vcs",
|
||||
"vcs": "hg"
|
||||
},
|
||||
{
|
||||
"name": "vcs.wiki"
|
||||
}
|
||||
],
|
||||
"archived": true,
|
||||
"archived_at": "1987-04-12T23:20:50.52Z",
|
||||
"created": "1985-04-12T23:20:50.52Z",
|
||||
"updated": "1986-04-12T23:20:50.52Z",
|
||||
"url": "https://example.com",
|
||||
"stars": "BBBBBB",
|
||||
"has_ci": true,
|
||||
"has_issues": true,
|
||||
"has_packages": true,
|
||||
"has_pull_requests": true,
|
||||
"has_wiki": true
|
||||
}
|
28
f3/file_format_testdata/project/good.json
Normal file
28
f3/file_format_testdata/project/good.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"index": "1",
|
||||
"name": "projectname",
|
||||
"is_private": false,
|
||||
"is_mirror": false,
|
||||
"description": "project description",
|
||||
"default_branch": "main",
|
||||
"repositories": [
|
||||
{
|
||||
"name": "vcs",
|
||||
"vcs": "hg"
|
||||
},
|
||||
{
|
||||
"name": "vcs.wiki"
|
||||
}
|
||||
],
|
||||
"archived": true,
|
||||
"archived_at": "1987-04-12T23:20:50.52Z",
|
||||
"created": "1985-04-12T23:20:50.52Z",
|
||||
"updated": "1986-04-12T23:20:50.52Z",
|
||||
"url": "https://example.com",
|
||||
"stars": 20,
|
||||
"has_ci": true,
|
||||
"has_issues": true,
|
||||
"has_packages": true,
|
||||
"has_pull_requests": true,
|
||||
"has_wiki": true
|
||||
}
|
2
f3/file_format_testdata/pullrequest/bad.json
Normal file
2
f3/file_format_testdata/pullrequest/bad.json
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
32
f3/file_format_testdata/pullrequest/good.json
Normal file
32
f3/file_format_testdata/pullrequest/good.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"index": "1",
|
||||
"poster_id": "/forgejo/users/1",
|
||||
"title": "title_a",
|
||||
"content": "content_a",
|
||||
"milestone": "../../milestones/5",
|
||||
"state": "closed",
|
||||
"is_locked": false,
|
||||
"created": "1985-04-12T23:20:50.52Z",
|
||||
"updated": "1986-04-12T23:20:50.52Z",
|
||||
"closed": "1986-04-12T23:20:50.52Z",
|
||||
"labels": [
|
||||
"../../labels/435"
|
||||
],
|
||||
"reactions": [],
|
||||
"assignees": [],
|
||||
"merged": false,
|
||||
"merged_time": "1986-04-12T23:20:50.52Z",
|
||||
"merged_commit_sha": "shashasha",
|
||||
"head": {
|
||||
"clone_url": "head_clone_url",
|
||||
"ref": "head_branch",
|
||||
"sha": "head_sha",
|
||||
"repository": "/forge/user/1/projects/2/repositories/vcs"
|
||||
},
|
||||
"base": {
|
||||
"clone_url": "base_clone_url",
|
||||
"ref": "base_branch",
|
||||
"sha": "base _sha",
|
||||
"repository": "/forge/user/3/projects/4/repositories/vcs"
|
||||
}
|
||||
}
|
2
f3/file_format_testdata/reaction/bad.json
Normal file
2
f3/file_format_testdata/reaction/bad.json
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
5
f3/file_format_testdata/reaction/good.json
Normal file
5
f3/file_format_testdata/reaction/good.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"index": "8",
|
||||
"user_id": "/forge/users/912",
|
||||
"content": "laugh"
|
||||
}
|
2
f3/file_format_testdata/release/bad.json
Normal file
2
f3/file_format_testdata/release/bad.json
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
11
f3/file_format_testdata/release/good.json
Normal file
11
f3/file_format_testdata/release/good.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"index": "123",
|
||||
"tag_name": "v12",
|
||||
"target_commitish": "stable",
|
||||
"name": "v12 name",
|
||||
"body": "v12 body",
|
||||
"draft": false,
|
||||
"prerelease": false,
|
||||
"publisher_id": "/forgejo/user/1",
|
||||
"created": "1985-04-12T23:20:50.52Z"
|
||||
}
|
2
f3/file_format_testdata/releaseasset/bad.json
Normal file
2
f3/file_format_testdata/releaseasset/bad.json
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
11
f3/file_format_testdata/releaseasset/good.json
Normal file
11
f3/file_format_testdata/releaseasset/good.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"index": "5",
|
||||
"name": "asset_5",
|
||||
"content_type": "application/zip",
|
||||
"size": 50,
|
||||
"download_count": 10,
|
||||
"download_url": "http://example.com/something",
|
||||
"created": "1985-04-12T23:20:50.52Z",
|
||||
"updated": "1986-04-12T23:20:50.52Z",
|
||||
"sha256": "4c5b2f412017de78124ae3a063d08e76566eea0cba6deb2533fb176d816a54fc"
|
||||
}
|
2
f3/file_format_testdata/repository/bad.json
Normal file
2
f3/file_format_testdata/repository/bad.json
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
4
f3/file_format_testdata/repository/good.json
Normal file
4
f3/file_format_testdata/repository/good.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "vcs.wiki",
|
||||
"vcs": "fossil"
|
||||
}
|
3
f3/file_format_testdata/review/bad.json
Normal file
3
f3/file_format_testdata/review/bad.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
}
|
||||
|
12
f3/file_format_testdata/review/good.json
Normal file
12
f3/file_format_testdata/review/good.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"index": "1",
|
||||
"reviewer_id": "/forge/user/50",
|
||||
"official": false,
|
||||
"commit_id": "shashashasha",
|
||||
"content": "cover review comment",
|
||||
"created_at": "1985-04-12T23:20:50.52Z",
|
||||
"state": "PENDING",
|
||||
"dissmissed": false,
|
||||
"stale": false
|
||||
}
|
||||
|
2
f3/file_format_testdata/reviewcomment/bad.json
Normal file
2
f3/file_format_testdata/reviewcomment/bad.json
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
14
f3/file_format_testdata/reviewcomment/good.json
Normal file
14
f3/file_format_testdata/reviewcomment/good.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"index": "100",
|
||||
"content": "review comment",
|
||||
"tree_path": "dir/file1.txt",
|
||||
"diff_hunk": "@@hunkhunk",
|
||||
"line": 1,
|
||||
"lines_count": 1,
|
||||
"commit_id": "shashashasha",
|
||||
"poster_id": "/forge/users/10",
|
||||
"reactions": [],
|
||||
"created_at": "1985-04-12T23:20:50.52Z",
|
||||
"updated_at": "1985-04-12T23:20:50.52Z",
|
||||
"resolver": "/forge/users/10"
|
||||
}
|
4
f3/file_format_testdata/topic.json
Normal file
4
f3/file_format_testdata/topic.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"index": "1",
|
||||
"name": "category1"
|
||||
}
|
2
f3/file_format_testdata/user/bad.json
Normal file
2
f3/file_format_testdata/user/bad.json
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
8
f3/file_format_testdata/user/good.json
Normal file
8
f3/file_format_testdata/user/good.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"index": "7",
|
||||
"name": "User Name One",
|
||||
"email": "user1@example.com",
|
||||
"username": "user1",
|
||||
"password": "gloxKainlo",
|
||||
"admin": false
|
||||
}
|
20
f3/forge.go
Normal file
20
f3/forge.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
type Forge struct {
|
||||
Common
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func (o Forge) Equal(other Forge) bool {
|
||||
return o.Common.Equal(other.Common)
|
||||
}
|
||||
|
||||
func (o *Forge) Clone() Interface {
|
||||
clone := &Forge{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
99
f3/formatbase.go
Normal file
99
f3/formatbase.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/util"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
GetID() string
|
||||
GetName() string
|
||||
SetID(id string)
|
||||
IsNil() bool
|
||||
GetReferences() References
|
||||
ToReference() *Reference
|
||||
Clone() Interface
|
||||
}
|
||||
|
||||
type ReferenceInterface interface {
|
||||
Get() string
|
||||
Set(reference string)
|
||||
GetIDAsString() string
|
||||
GetIDAsInt() int64
|
||||
}
|
||||
|
||||
type Reference struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
func (r *Reference) Get() string {
|
||||
return r.ID
|
||||
}
|
||||
|
||||
func (r *Reference) Set(reference string) {
|
||||
r.ID = reference
|
||||
}
|
||||
|
||||
func (r *Reference) GetIDAsInt() int64 {
|
||||
return util.ParseInt(r.GetIDAsString())
|
||||
}
|
||||
|
||||
func (r *Reference) GetIDAsString() string {
|
||||
s := strings.Split(r.ID, "/")
|
||||
return s[len(s)-1]
|
||||
}
|
||||
|
||||
type References []ReferenceInterface
|
||||
|
||||
func (r *Reference) UnmarshalJSON(b []byte) error {
|
||||
return json.Unmarshal(b, &r.ID)
|
||||
}
|
||||
|
||||
func (r Reference) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(r.ID)
|
||||
}
|
||||
|
||||
func (r *Reference) GetID() string { return r.ID }
|
||||
func (r *Reference) SetID(id string) { r.ID = id }
|
||||
func (r *Reference) IsNil() bool { return r == nil || r.ID == "0" || r.ID == "" }
|
||||
func (r Reference) Equal(other Reference) bool { return r.ID == other.ID }
|
||||
|
||||
func NewReferences() References {
|
||||
return make([]ReferenceInterface, 0, 1)
|
||||
}
|
||||
|
||||
func NewReference(id string) *Reference {
|
||||
r := &Reference{}
|
||||
r.SetID(id)
|
||||
return r
|
||||
}
|
||||
|
||||
type Common struct {
|
||||
Index Reference `json:"index"`
|
||||
}
|
||||
|
||||
func (c *Common) GetID() string { return c.Index.GetID() }
|
||||
func (c *Common) GetName() string { return c.GetID() }
|
||||
func (c *Common) SetID(id string) { c.Index.SetID(id) }
|
||||
func (c *Common) IsNil() bool { return c == nil || c.Index.IsNil() }
|
||||
func (c *Common) GetReferences() References { return NewReferences() }
|
||||
func (c *Common) ToReference() *Reference { return &c.Index }
|
||||
func (c Common) Equal(other Common) bool { return true }
|
||||
|
||||
var Nil = &Common{}
|
||||
|
||||
func NewCommon(id string) Common {
|
||||
return Common{Index: *NewReference(id)}
|
||||
}
|
||||
|
||||
func (c *Common) Clone() Interface {
|
||||
clone := &Common{}
|
||||
*clone = *c
|
||||
return clone
|
||||
}
|
57
f3/formatbase_test.go
Normal file
57
f3/formatbase_test.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright limiting-factor <limiting-factor@posteo.com>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestF3Reference(t *testing.T) {
|
||||
ref := "reference"
|
||||
r := NewReference(ref)
|
||||
assert.Equal(t, ref, r.GetID())
|
||||
otherRef := "other"
|
||||
r.SetID(otherRef)
|
||||
assert.Equal(t, otherRef, r.GetID())
|
||||
r.Set(otherRef)
|
||||
assert.Equal(t, otherRef, r.Get())
|
||||
assert.True(t, r.Equal(*r))
|
||||
|
||||
m, err := r.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
u := NewReference("???")
|
||||
require.NoError(t, u.UnmarshalJSON(m))
|
||||
assert.True(t, r.Equal(*u))
|
||||
|
||||
assert.False(t, r.IsNil())
|
||||
r.SetID("")
|
||||
assert.True(t, r.IsNil())
|
||||
r.SetID("0")
|
||||
assert.True(t, r.IsNil())
|
||||
|
||||
var nilRef *Reference
|
||||
assert.True(t, nilRef.IsNil())
|
||||
}
|
||||
|
||||
func TestF3Common(t *testing.T) {
|
||||
id := "ID"
|
||||
c := NewCommon(id)
|
||||
assert.Equal(t, id, c.GetID())
|
||||
assert.Equal(t, id, c.GetName())
|
||||
otherID := "otherID"
|
||||
c.SetID(otherID)
|
||||
assert.Equal(t, otherID, c.GetID())
|
||||
|
||||
assert.False(t, c.IsNil())
|
||||
c.SetID("")
|
||||
assert.True(t, c.IsNil())
|
||||
c.SetID("0")
|
||||
assert.True(t, c.IsNil())
|
||||
|
||||
var nilCommon *Common
|
||||
assert.True(t, nilCommon.IsNil())
|
||||
}
|
58
f3/issue.go
Normal file
58
f3/issue.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
IssueStateOpen = "open"
|
||||
IssueStateClosed = "closed"
|
||||
)
|
||||
|
||||
type Issue struct {
|
||||
Common
|
||||
PosterID *Reference `json:"poster_id"`
|
||||
Assignees []*Reference `json:"assignees"`
|
||||
Labels []*Reference `json:"labels"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Milestone *Reference `json:"milestone"`
|
||||
State string `json:"state"` // open, closed
|
||||
IsLocked bool `json:"is_locked"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Closed *time.Time `json:"closed"`
|
||||
}
|
||||
|
||||
func (o *Issue) GetReferences() References {
|
||||
references := o.Common.GetReferences()
|
||||
for _, assignee := range o.Assignees {
|
||||
references = append(references, assignee)
|
||||
}
|
||||
for _, label := range o.Labels {
|
||||
references = append(references, label)
|
||||
}
|
||||
if !o.Milestone.IsNil() {
|
||||
references = append(references, o.Milestone)
|
||||
}
|
||||
return append(references, o.PosterID)
|
||||
}
|
||||
|
||||
func (o Issue) Equal(other Issue) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
nilOrEqual(o.PosterID, other.PosterID) &&
|
||||
arrayEqual(o.Assignees, other.Assignees) &&
|
||||
arrayEqual(o.Labels, other.Labels) &&
|
||||
o.Title == other.Title &&
|
||||
nilOrEqual(o.Milestone, other.Milestone) &&
|
||||
o.State == other.State &&
|
||||
o.IsLocked == other.IsLocked
|
||||
}
|
||||
|
||||
func (o *Issue) Clone() Interface {
|
||||
clone := &Issue{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
25
f3/label.go
Normal file
25
f3/label.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
type Label struct {
|
||||
Common
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func (o Label) Equal(other Label) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
o.Name == other.Name &&
|
||||
o.Color == other.Color &&
|
||||
o.Description == other.Description
|
||||
}
|
||||
|
||||
func (o *Label) Clone() Interface {
|
||||
clone := &Label{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
37
f3/milestone.go
Normal file
37
f3/milestone.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
MilestoneStateOpen = "open"
|
||||
MilestoneStateClosed = "closed"
|
||||
)
|
||||
|
||||
type Milestone struct {
|
||||
Common
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Deadline *time.Time `json:"deadline"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated *time.Time `json:"updated"`
|
||||
Closed *time.Time `json:"closed"`
|
||||
State string `json:"state"` // open, closed
|
||||
}
|
||||
|
||||
func (o Milestone) Equal(other Milestone) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
o.Title == other.Title &&
|
||||
o.Description == other.Description &&
|
||||
nilOrEqualTimeToDate(o.Deadline, other.Deadline) &&
|
||||
o.State == other.State
|
||||
}
|
||||
|
||||
func (o *Milestone) Clone() Interface {
|
||||
clone := &Milestone{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
81
f3/new.go
Normal file
81
f3/new.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func New(name string) Interface {
|
||||
common := NewCommon(name)
|
||||
switch name {
|
||||
case "<root>":
|
||||
return &common
|
||||
case ResourceAsset:
|
||||
return &ReleaseAsset{}
|
||||
case ResourceAssets:
|
||||
return &common
|
||||
case ResourceComment:
|
||||
return &Comment{}
|
||||
case ResourceComments:
|
||||
return &common
|
||||
case ResourceIssue:
|
||||
return &Issue{}
|
||||
case ResourceIssues:
|
||||
return &common
|
||||
case ResourceLabel:
|
||||
return &Label{}
|
||||
case ResourceLabels:
|
||||
return &common
|
||||
case ResourceMilestone:
|
||||
return &Milestone{}
|
||||
case ResourceMilestones:
|
||||
return &common
|
||||
case ResourceOrganization:
|
||||
return &Organization{}
|
||||
case ResourceOrganizations:
|
||||
return &common
|
||||
case ResourceProject:
|
||||
return &Project{}
|
||||
case ResourceProjects:
|
||||
return &common
|
||||
case ResourcePullRequest:
|
||||
return &PullRequest{}
|
||||
case ResourcePullRequests:
|
||||
return &common
|
||||
case ResourceReaction:
|
||||
return &Reaction{}
|
||||
case ResourceReactions:
|
||||
return &common
|
||||
case ResourceRelease:
|
||||
return &Release{}
|
||||
case ResourceReleases:
|
||||
return &common
|
||||
case ResourceRepository:
|
||||
return &Repository{}
|
||||
case ResourceRepositories:
|
||||
return &common
|
||||
case ResourceReview:
|
||||
return &Review{}
|
||||
case ResourceReviews:
|
||||
return &common
|
||||
case ResourceReviewComment:
|
||||
return &ReviewComment{}
|
||||
case ResourceReviewComments:
|
||||
return &common
|
||||
case ResourceTopic:
|
||||
return &Topic{}
|
||||
case ResourceTopics:
|
||||
return &common
|
||||
case ResourceUser:
|
||||
return &User{}
|
||||
case ResourceUsers:
|
||||
return &common
|
||||
case ResourceForge:
|
||||
return &Forge{}
|
||||
default:
|
||||
panic(fmt.Errorf("unknown %s", name))
|
||||
}
|
||||
}
|
27
f3/organization.go
Normal file
27
f3/organization.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
type Organization struct {
|
||||
Common
|
||||
FullName string `json:"full_name"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (o Organization) Equal(other Organization) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
o.FullName == other.FullName &&
|
||||
o.Name == other.Name
|
||||
}
|
||||
|
||||
func (o *Organization) GetName() string {
|
||||
return o.Name
|
||||
}
|
||||
|
||||
func (o *Organization) Clone() Interface {
|
||||
clone := &Organization{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
50
f3/project.go
Normal file
50
f3/project.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
type Project struct {
|
||||
Common
|
||||
Name string `json:"name"`
|
||||
IsPrivate bool `json:"is_private"`
|
||||
IsMirror bool `json:"is_mirror"`
|
||||
Description string `json:"description"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
Forked *Reference `json:"forked"`
|
||||
HasWiki bool `json:"has_wiki"`
|
||||
Topics []*Reference `json:"topics"`
|
||||
}
|
||||
|
||||
func (o Project) Project(other Project) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
o.Name == other.Name &&
|
||||
o.IsPrivate == other.IsPrivate &&
|
||||
o.IsMirror == other.IsMirror &&
|
||||
o.Description == other.Description &&
|
||||
o.DefaultBranch == other.DefaultBranch &&
|
||||
nilOrEqual(o.Forked, other.Forked) &&
|
||||
o.HasWiki == other.HasWiki &&
|
||||
arrayEqual(o.Topics, other.Topics)
|
||||
}
|
||||
|
||||
func (o *Project) GetName() string {
|
||||
return o.Name
|
||||
}
|
||||
|
||||
func (o *Project) GetReferences() References {
|
||||
references := o.Common.GetReferences()
|
||||
if !o.Forked.IsNil() {
|
||||
references = append(references, o.Forked)
|
||||
}
|
||||
for _, topic := range o.Topics {
|
||||
references = append(references, topic)
|
||||
}
|
||||
return references
|
||||
}
|
||||
|
||||
func (o *Project) Clone() Interface {
|
||||
clone := &Project{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
72
f3/pullrequest.go
Normal file
72
f3/pullrequest.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PullRequestFetchFunc func(ctx context.Context, url, ref string)
|
||||
|
||||
const (
|
||||
PullRequestStateOpen = "open"
|
||||
PullRequestStateClosed = "closed"
|
||||
)
|
||||
|
||||
type PullRequest struct {
|
||||
Common
|
||||
PosterID *Reference `json:"poster_id"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Milestone *Reference `json:"milestone"`
|
||||
State string `json:"state"` // open, closed
|
||||
IsLocked bool `json:"is_locked"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Closed *time.Time `json:"closed"`
|
||||
Merged bool `json:"merged"`
|
||||
MergedTime *time.Time `json:"merged_time"`
|
||||
MergeCommitSHA string `json:"merged_commit_sha"`
|
||||
Head PullRequestBranch `json:"head"`
|
||||
Base PullRequestBranch `json:"base"`
|
||||
|
||||
FetchFunc PullRequestFetchFunc `json:"-"`
|
||||
}
|
||||
|
||||
func (o PullRequest) Equal(other PullRequest) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
nilOrEqual(o.PosterID, other.PosterID) &&
|
||||
o.Title == other.Title &&
|
||||
o.Content == other.Content &&
|
||||
nilOrEqual(o.Milestone, other.Milestone) &&
|
||||
o.State == other.State &&
|
||||
o.IsLocked == other.IsLocked &&
|
||||
o.Merged == other.Merged &&
|
||||
nilOrEqual(o.MergedTime, other.MergedTime) &&
|
||||
o.MergeCommitSHA == other.MergeCommitSHA &&
|
||||
o.Head.Equal(other.Head) &&
|
||||
o.Base.Equal(other.Base)
|
||||
}
|
||||
|
||||
func (o *PullRequest) GetReferences() References {
|
||||
references := o.Common.GetReferences()
|
||||
if !o.Milestone.IsNil() {
|
||||
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.Repository != o.Base.Repository
|
||||
}
|
||||
|
||||
func (o *PullRequest) Clone() Interface {
|
||||
clone := &PullRequest{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
20
f3/pullrequestbranch.go
Normal file
20
f3/pullrequestbranch.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
type PullRequestBranch struct {
|
||||
Ref string `json:"ref"`
|
||||
SHA string `json:"sha"`
|
||||
Repository *Reference `json:"repository"`
|
||||
}
|
||||
|
||||
func (o PullRequestBranch) Equal(other PullRequestBranch) bool {
|
||||
return o.Ref == other.Ref &&
|
||||
o.SHA == other.SHA
|
||||
}
|
||||
|
||||
func (o *PullRequestBranch) GetReferences() References {
|
||||
return References{o.Repository}
|
||||
}
|
31
f3/reaction.go
Normal file
31
f3/reaction.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
type Reaction struct {
|
||||
Common
|
||||
UserID *Reference `json:"user_id"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func (o Reaction) Equal(other Reaction) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
nilOrEqual(o.UserID, other.UserID) &&
|
||||
o.Content == other.Content
|
||||
}
|
||||
|
||||
func (o *Reaction) GetReferences() References {
|
||||
references := o.Common.GetReferences()
|
||||
if !o.UserID.IsNil() {
|
||||
references = append(references, o.UserID)
|
||||
}
|
||||
return references
|
||||
}
|
||||
|
||||
func (o *Reaction) Clone() Interface {
|
||||
clone := &Reaction{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
48
f3/release.go
Normal file
48
f3/release.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
Common
|
||||
TagName string `json:"tag_name"`
|
||||
TargetCommitish string `json:"target_commitish"`
|
||||
Name string `json:"name"`
|
||||
Body string `json:"body"`
|
||||
Draft bool `json:"draft"`
|
||||
Prerelease bool `json:"prerelease"`
|
||||
PublisherID *Reference `json:"publisher_id"`
|
||||
Assets []*ReleaseAsset `json:"assets"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
func (o Release) Equal(other Release) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
o.TagName == other.TagName &&
|
||||
o.TargetCommitish == other.TargetCommitish &&
|
||||
o.Name == other.Name &&
|
||||
o.Body == other.Body &&
|
||||
o.Draft == other.Draft &&
|
||||
o.Prerelease == other.Prerelease &&
|
||||
nilOrEqual(o.PublisherID, other.PublisherID) &&
|
||||
arrayEqual(o.Assets, other.Assets)
|
||||
}
|
||||
|
||||
func (o *Release) GetReferences() References {
|
||||
references := o.Common.GetReferences()
|
||||
if !o.PublisherID.IsNil() {
|
||||
references = append(references, o.PublisherID)
|
||||
}
|
||||
return references
|
||||
}
|
||||
|
||||
func (o *Release) Clone() Interface {
|
||||
clone := &Release{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
38
f3/releaseasset.go
Normal file
38
f3/releaseasset.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DownloadFuncType func() io.ReadCloser
|
||||
|
||||
type ReleaseAsset struct {
|
||||
Common
|
||||
Name string `json:"name"`
|
||||
ContentType string `json:"content_type"`
|
||||
Size int64 `json:"size"`
|
||||
DownloadCount int64 `json:"download_count"`
|
||||
Created time.Time `json:"created"`
|
||||
SHA256 string `json:"sha256"`
|
||||
DownloadURL string `json:"download_url"`
|
||||
DownloadFunc DownloadFuncType `json:"-"`
|
||||
}
|
||||
|
||||
func (o ReleaseAsset) Equal(other ReleaseAsset) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
o.Name == other.Name &&
|
||||
o.ContentType == other.ContentType &&
|
||||
o.Size == other.Size &&
|
||||
o.SHA256 == other.SHA256
|
||||
}
|
||||
|
||||
func (o *ReleaseAsset) Clone() Interface {
|
||||
clone := &ReleaseAsset{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
45
f3/repository.go
Normal file
45
f3/repository.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const (
|
||||
RepositoryNameDefault = "vcs"
|
||||
RepositoryNameWiki = "vcs.wiki"
|
||||
)
|
||||
|
||||
var nameToID = map[string]int64{
|
||||
RepositoryNameDefault: 1,
|
||||
RepositoryNameWiki: 2,
|
||||
}
|
||||
|
||||
// var RepositoryNames = []string{RepositoryNameDefault, RepositoryNameWiki}
|
||||
|
||||
var RepositoryNames = []string{RepositoryNameDefault}
|
||||
|
||||
type Repository struct {
|
||||
Common
|
||||
|
||||
Name string
|
||||
FetchFunc func(ctx context.Context, destination string, internalRefs []string) `json:"-"`
|
||||
}
|
||||
|
||||
func (o Repository) Equal(other Repository) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
o.Name == other.Name
|
||||
}
|
||||
|
||||
func (o *Repository) Clone() Interface {
|
||||
clone := &Repository{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
||||
|
||||
func RepositoryDirname(name string) string {
|
||||
return "git" + name
|
||||
}
|
39
f3/resources.go
Normal file
39
f3/resources.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
const (
|
||||
ResourceAsset = "asset"
|
||||
ResourceAssets = "assets"
|
||||
ResourceComment = "comment"
|
||||
ResourceComments = "comments"
|
||||
ResourceIssue = "issue"
|
||||
ResourceIssues = "issues"
|
||||
ResourceLabel = "label"
|
||||
ResourceLabels = "labels"
|
||||
ResourceMilestone = "milestone"
|
||||
ResourceMilestones = "milestones"
|
||||
ResourceOrganization = "organization"
|
||||
ResourceOrganizations = "organizations"
|
||||
ResourceProject = "project"
|
||||
ResourceProjects = "projects"
|
||||
ResourcePullRequest = "pull_request"
|
||||
ResourcePullRequests = "pull_requests"
|
||||
ResourceReaction = "reaction"
|
||||
ResourceReactions = "reactions"
|
||||
ResourceRelease = "release"
|
||||
ResourceReleases = "releases"
|
||||
ResourceRepository = "repository"
|
||||
ResourceRepositories = "repositories"
|
||||
ResourceReview = "review"
|
||||
ResourceReviews = "reviews"
|
||||
ResourceReviewComment = "reviewcomment"
|
||||
ResourceReviewComments = "reviewcomments"
|
||||
ResourceTopic = "topic"
|
||||
ResourceTopics = "topics"
|
||||
ResourceUser = "user"
|
||||
ResourceUsers = "users"
|
||||
ResourceForge = "forge"
|
||||
)
|
48
f3/review.go
Normal file
48
f3/review.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
ReviewStatePending = "PENDING"
|
||||
ReviewStateApproved = "APPROVED"
|
||||
ReviewStateChangesRequested = "CHANGES_REQUESTED"
|
||||
ReviewStateCommented = "COMMENTED"
|
||||
ReviewStateRequestReview = "REQUEST_REVIEW"
|
||||
ReviewStateUnknown = ""
|
||||
)
|
||||
|
||||
type Review struct {
|
||||
Common
|
||||
ReviewerID *Reference `json:"reviewer_id"`
|
||||
Official bool `json:"official"`
|
||||
CommitID string `json:"commit_id"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
func (o Review) Equal(other Review) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
nilOrEqual(o.ReviewerID, other.ReviewerID) &&
|
||||
o.CommitID == other.CommitID &&
|
||||
o.Content == other.Content &&
|
||||
o.State == other.State
|
||||
}
|
||||
|
||||
func (o *Review) GetReferences() References {
|
||||
references := o.Common.GetReferences()
|
||||
if !o.ReviewerID.IsNil() {
|
||||
references = append(references, o.ReviewerID)
|
||||
}
|
||||
return references
|
||||
}
|
||||
|
||||
func (o *Review) Clone() Interface {
|
||||
clone := &Review{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
46
f3/reviewcomment.go
Normal file
46
f3/reviewcomment.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ReviewComment struct {
|
||||
Common
|
||||
Content string `json:"content"`
|
||||
TreePath string `json:"tree_path"`
|
||||
DiffHunk string `json:"diff_hunk"`
|
||||
Line int `json:"line"`
|
||||
LinesCount int `json:"lines_count"`
|
||||
CommitID string `json:"commit_id"`
|
||||
PosterID *Reference `json:"poster_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (o ReviewComment) Equal(other ReviewComment) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
o.Content == other.Content &&
|
||||
o.TreePath == other.TreePath &&
|
||||
o.Line == other.Line &&
|
||||
o.LinesCount == other.LinesCount &&
|
||||
o.CommitID == other.CommitID &&
|
||||
nilOrEqual(o.PosterID, other.PosterID)
|
||||
}
|
||||
|
||||
func (o *ReviewComment) GetReferences() References {
|
||||
references := o.Common.GetReferences()
|
||||
if !o.PosterID.IsNil() {
|
||||
references = append(references, o.PosterID)
|
||||
}
|
||||
return references
|
||||
}
|
||||
|
||||
func (o *ReviewComment) Clone() Interface {
|
||||
clone := &ReviewComment{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
42
f3/schemas/ci.json
Normal file
42
f3/schemas/ci.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"title": "CI",
|
||||
"description": "The Continuous Integration supported by the project. The configuration files are found in the repository itself, under a path that depends on the CI system.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "The type of continuous integration",
|
||||
"enum": [
|
||||
"Apache Gump",
|
||||
"Azure DevOps Server",
|
||||
"Bamboo",
|
||||
"Buddy",
|
||||
"Buildbot",
|
||||
"BuildMaster",
|
||||
"CircleCI",
|
||||
"Drone",
|
||||
"Forgejo Actions",
|
||||
"Gitea Actions",
|
||||
"GitHub Actions",
|
||||
"GitLab",
|
||||
"GoCD",
|
||||
"Jenkins",
|
||||
"OpenMake",
|
||||
"Semaphore",
|
||||
"TeamCity",
|
||||
"tekton",
|
||||
"Travis CI",
|
||||
"Vexor",
|
||||
"Woodpecker CI"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/ci.json",
|
||||
"$$target": "ci.json"
|
||||
}
|
49
f3/schemas/comment.json
Normal file
49
f3/schemas/comment.json
Normal file
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"title": "Comment",
|
||||
"description": "Comment associated to a commentable object (i.e. issue, review, etc.). Forge users add a comment to an object to create a non-threaded conversation.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier of the comment.",
|
||||
"type": "string"
|
||||
},
|
||||
"poster_id": {
|
||||
"description": "Unique identifier of the comment author.",
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"description": "Creation time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updated": {
|
||||
"description": "Last update time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"content": {
|
||||
"description": "Markdown content of the comment.",
|
||||
"type": "string"
|
||||
},
|
||||
"reactions": {
|
||||
"description": "List of reactions.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "reaction.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"poster_id",
|
||||
"created",
|
||||
"updated",
|
||||
"content"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/comment.json",
|
||||
"$$target": "comment.json"
|
||||
}
|
22
f3/schemas/index.rst
Normal file
22
f3/schemas/index.rst
Normal file
|
@ -0,0 +1,22 @@
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
.. jsonschema:: ci.json
|
||||
.. jsonschema:: comment.json
|
||||
.. jsonschema:: issue.json
|
||||
.. jsonschema:: label.json
|
||||
.. jsonschema:: milestone.json
|
||||
.. jsonschema:: object.json
|
||||
.. jsonschema:: organization.json
|
||||
.. jsonschema:: project.json
|
||||
.. jsonschema:: pullrequest.json
|
||||
.. jsonschema:: pullrequestbranch.json
|
||||
.. jsonschema:: reaction.json
|
||||
.. jsonschema:: release.json
|
||||
.. jsonschema:: releaseasset.json
|
||||
.. jsonschema:: repository.json
|
||||
.. jsonschema:: review.json
|
||||
.. jsonschema:: reviewcomment.json
|
||||
.. jsonschema:: topic.json
|
||||
.. jsonschema:: user.json
|
||||
|
96
f3/schemas/issue.json
Normal file
96
f3/schemas/issue.json
Normal file
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"title": "Issue",
|
||||
"description": "An issue within an issue tracking system, relative to a project.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier of the issue.",
|
||||
"type": "string"
|
||||
},
|
||||
"poster_id": {
|
||||
"description": "Unique identifier of the user who authored the issue.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Short description displayed as the title.",
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"description": "Description of the issue.",
|
||||
"type": "string"
|
||||
},
|
||||
"milestone": {
|
||||
"description": "Unique identifier of the milestone.",
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"description": "An issue is 'closed' when it is resolved, 'open' otherwise. Issues that do not relate to a topic that needs to be resolved, such as an open conversation, may never be closed.",
|
||||
"enum": [
|
||||
"closed",
|
||||
"open"
|
||||
]
|
||||
},
|
||||
"is_locked": {
|
||||
"description": "A locked issue can only be modified by privileged users. It is commonly used for moderation purposes when comments associated with the issue are too heated.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"created": {
|
||||
"description": "Creation time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updated": {
|
||||
"description": "Last update time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"closed": {
|
||||
"description": "The last time 'state' changed to 'closed'.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"due": {
|
||||
"description": "Due date.",
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"labels": {
|
||||
"description": "List of label unique identifiers.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"reactions": {
|
||||
"description": "List of reactions.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "reaction.json"
|
||||
}
|
||||
},
|
||||
"assignees": {
|
||||
"description": "List of assignees.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"description": "Name of a user assigned to the issue.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"poster_id",
|
||||
"title",
|
||||
"content",
|
||||
"state",
|
||||
"is_locked",
|
||||
"created",
|
||||
"updated"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/issue.json",
|
||||
"$$target": "issue.json"
|
||||
}
|
37
f3/schemas/label.json
Normal file
37
f3/schemas/label.json
Normal file
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"title": "Label",
|
||||
"description": "Label associated to an issue.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the label, unique within the repository.",
|
||||
"type": "string"
|
||||
},
|
||||
"color": {
|
||||
"description": "Color code of the label in RGB notation 'xxx' or 'xxxxxx'.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Long description.",
|
||||
"type": "string"
|
||||
},
|
||||
"exclusive": {
|
||||
"description": "There can only be one label with the prefix found before the first slash (/).",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"name"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/label.json",
|
||||
"$$target": "label.json"
|
||||
}
|
62
f3/schemas/milestone.json
Normal file
62
f3/schemas/milestone.json
Normal file
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"title": "Milestone",
|
||||
"description": "Milestone relative to a project, for the purpose of grouping objects due to a given date (issues, etc.).",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Short description.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Long description.",
|
||||
"type": "string"
|
||||
},
|
||||
"deadline": {
|
||||
"description": "Deadline after which the milestone is overdue.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"created": {
|
||||
"description": "Creation time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updated": {
|
||||
"description": "Last update time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"closed": {
|
||||
"description": "The last time 'state' changed to 'closed'.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"state": {
|
||||
"description": "A 'closed' milestone will not see any activity in the future, otherwise it is 'open'.",
|
||||
"enum": [
|
||||
"closed",
|
||||
"open"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"title",
|
||||
"description",
|
||||
"deadline",
|
||||
"created",
|
||||
"updated",
|
||||
"closed",
|
||||
"state"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/milestone.json",
|
||||
"$$target": "milestone.json"
|
||||
}
|
35
f3/schemas/object.json
Normal file
35
f3/schemas/object.json
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"title": "Object",
|
||||
"description": "Meta information and reference to an opaque content such as an image. The unique identifier is the SHA-256 of the content of the object.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier.",
|
||||
"type": "string"
|
||||
},
|
||||
"mime": {
|
||||
"description": "Mime type of the object.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Human readable file name.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Description.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"mime",
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/object.json",
|
||||
"$$target": "object.json"
|
||||
}
|
29
f3/schemas/organization.json
Normal file
29
f3/schemas/organization.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"title": "Organization",
|
||||
"description": "A forge organization.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier of the organization.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Unique name of the organization.",
|
||||
"type": "string"
|
||||
},
|
||||
"full_name": {
|
||||
"description": "Readable name of the organization.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"name"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/organization.json",
|
||||
"$$target": "organization.json"
|
||||
}
|
117
f3/schemas/project.json
Normal file
117
f3/schemas/project.json
Normal file
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"title": "Project",
|
||||
"description": "A software project contains a code repository, an issue tracker, etc.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier of the project.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the project, relative to the owner.",
|
||||
"type": "string"
|
||||
},
|
||||
"is_private": {
|
||||
"description": "True if the visibility of the project is not public.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_mirror": {
|
||||
"description": "True if it is a mirror of a project residing on another forge.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"description": "Long description of the project.",
|
||||
"type": "string"
|
||||
},
|
||||
"default_branch": {
|
||||
"description": "Name of the default branch in the code repository.",
|
||||
"type": "string"
|
||||
},
|
||||
"repositories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "repository.json"
|
||||
}
|
||||
},
|
||||
"forked": {
|
||||
"description": "Unique identifier of the project from which this one was forked.",
|
||||
"type": "string"
|
||||
},
|
||||
"ci": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "ci.json"
|
||||
}
|
||||
},
|
||||
"archived": {
|
||||
"description": "True if archived and read only.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"archived_at": {
|
||||
"description": "Time of archival.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"created": {
|
||||
"description": "Creation time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updated": {
|
||||
"description": "Last update time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"url": {
|
||||
"description": "URL associated with the project, for instance the project home page.",
|
||||
"type": "string"
|
||||
},
|
||||
"stars": {
|
||||
"description": "Number of stars.",
|
||||
"type": "number"
|
||||
},
|
||||
"has_ci": {
|
||||
"description": "True if CI is enabled.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"has_issues": {
|
||||
"description": "True if the issue tracker is enabled.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"has_packages": {
|
||||
"description": "True if the software packages are enabled.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"has_kanban": {
|
||||
"description": "True if the kanban is enabled.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"has_pull_requests": {
|
||||
"description": "True if pull requests are enabled.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"has_releases": {
|
||||
"description": "True if releases are enabled.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"has_wiki": {
|
||||
"description": "True if the wiki is enabled.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"name",
|
||||
"is_private",
|
||||
"is_mirror",
|
||||
"description",
|
||||
"default_branch",
|
||||
"repositories"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/project.json",
|
||||
"$$target": "project.json"
|
||||
}
|
134
f3/schemas/pullrequest.json
Normal file
134
f3/schemas/pullrequest.json
Normal file
|
@ -0,0 +1,134 @@
|
|||
{
|
||||
"title": "Pull request",
|
||||
"description": "A pull requests to merge a commit from a 'head' that may be another branch in the same repository or a branch in a forked repository.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier of the pull request.",
|
||||
"type": "string"
|
||||
},
|
||||
"poster_id": {
|
||||
"description": "Unique identifier of the user who authored the pull request.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Short description displayed as the title.",
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"description": "Long description.",
|
||||
"type": "string"
|
||||
},
|
||||
"milestone": {
|
||||
"description": "Unique identifier of the milestone.",
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"description": "A 'closed' pull request will not see any activity in the future, otherwise it is 'open'.",
|
||||
"enum": [
|
||||
"closed",
|
||||
"open"
|
||||
]
|
||||
},
|
||||
"is_locked": {
|
||||
"description": "A locked pull request can only be modified by privileged users.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"created": {
|
||||
"description": "Creation time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updated": {
|
||||
"description": "Last update time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"closed": {
|
||||
"description": "The last time 'state' changed to 'closed'.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"labels": {
|
||||
"description": "List of labels unique identifiers.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"reactions": {
|
||||
"description": "List of reactions.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "reaction.json"
|
||||
}
|
||||
},
|
||||
"assignees": {
|
||||
"description": "List of assignees.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"description": "Name of a user assigned to the issue.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"merged": {
|
||||
"description": "True if the pull request was merged.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"merged_time": {
|
||||
"description": "The time when the pull request was merged.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"merged_commit_sha": {
|
||||
"description": "The SHA of the merge commit.",
|
||||
"type": "string"
|
||||
},
|
||||
"head": {
|
||||
"description": "The changes proposed in the pull request.",
|
||||
"type": "object",
|
||||
"items": {
|
||||
"$ref": "pullrequestbranch.json"
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"description": "The branch where the pull request changes in the head are to be merged.",
|
||||
"type": "object",
|
||||
"items": {
|
||||
"$ref": "pullrequestbranch.json"
|
||||
}
|
||||
},
|
||||
"merged_by": {
|
||||
"description": "Unique identifier of the user who merged the pull request.",
|
||||
"type": "string"
|
||||
},
|
||||
"due": {
|
||||
"description": "Due date.",
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"allow_edit": {
|
||||
"description": "True when the author of the pull request allows pushing new commits to its branch.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"poster_id",
|
||||
"title",
|
||||
"content",
|
||||
"state",
|
||||
"is_locked",
|
||||
"created",
|
||||
"updated",
|
||||
"merged",
|
||||
"head",
|
||||
"base"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/pullrequest.json",
|
||||
"$$target": "pullrequest.json"
|
||||
}
|
30
f3/schemas/pullrequestbranch.json
Normal file
30
f3/schemas/pullrequestbranch.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"title": "Pull request reference to a commit",
|
||||
"description": "The location of a commit and the repository where it can be found.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ref": {
|
||||
"description": "Repository reference of the commit (branch, tag, etc.).",
|
||||
"type": "string"
|
||||
},
|
||||
"sha": {
|
||||
"description": "SHA of the commit.",
|
||||
"type": "string"
|
||||
},
|
||||
"repository": {
|
||||
"description": "Unique identifier of the repository.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ref",
|
||||
"sha",
|
||||
"repository"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/pullrequestbranch.json",
|
||||
"$$target": "pullrequestbranch.json"
|
||||
}
|
30
f3/schemas/reaction.json
Normal file
30
f3/schemas/reaction.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"title": "Reaction",
|
||||
"description": "Reaction associated to a comment that is displayed as a single emoji.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier of the reaction.",
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"description": "Unique identifier of the user who authored the reaction.",
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"description": "Representation of the reaction. The rendering of the reaction depends on the forge displaying it.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"user_id",
|
||||
"content"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/reaction.json",
|
||||
"$$target": "reaction.json"
|
||||
}
|
60
f3/schemas/release.json
Normal file
60
f3/schemas/release.json
Normal file
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"title": "Release",
|
||||
"description": "A release is associated with a tag in a repository and contains of a set of files (release assets).",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier of the release.",
|
||||
"type": "string"
|
||||
},
|
||||
"tag_name": {
|
||||
"description": "Tag name of the release.",
|
||||
"type": "string"
|
||||
},
|
||||
"target_commitish": {
|
||||
"description": "Specifies the commitish value that determines where the tag is created from. Can be any branch or commit SHA. Unused if the tag already exists.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the release.",
|
||||
"type": "string"
|
||||
},
|
||||
"body": {
|
||||
"description": "Text describing the contents of the release, usually the release notes.",
|
||||
"type": "string"
|
||||
},
|
||||
"draft": {
|
||||
"description": "True if the release is a draft.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"prerelease": {
|
||||
"description": "True if the release is a pre-release.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"publisher_id": {
|
||||
"description": "Unique identifier of the user who authored the release.",
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"description": "Creation time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"tag_name",
|
||||
"name",
|
||||
"body",
|
||||
"draft",
|
||||
"prerelease",
|
||||
"publisher_id",
|
||||
"created"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/release.json",
|
||||
"$$target": "release.json"
|
||||
}
|
61
f3/schemas/releaseasset.json
Normal file
61
f3/schemas/releaseasset.json
Normal file
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"title": "Release asset",
|
||||
"description": "A file associated with a release. The content of the file is opaque.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier of the release asset.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the release asset.",
|
||||
"type": "string"
|
||||
},
|
||||
"content_type": {
|
||||
"description": "The content type of the release asset (application/zip, etc.).",
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"description": "Size in bytes of the release asset.",
|
||||
"type": "number"
|
||||
},
|
||||
"download_count": {
|
||||
"description": "The number of times the release asset was downloaded.",
|
||||
"type": "number"
|
||||
},
|
||||
"download_url": {
|
||||
"description": "The URL from which the release asset can be downloaded.",
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"description": "Creation time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updated": {
|
||||
"description": "Last update time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"sha256": {
|
||||
"description": "SHA256 of the cnotent of the asset.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"name",
|
||||
"content_type",
|
||||
"size",
|
||||
"download_count",
|
||||
"created",
|
||||
"updated",
|
||||
"sha256"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/releaseasset.json",
|
||||
"$$target": "releaseasset.json"
|
||||
}
|
31
f3/schemas/repository.json
Normal file
31
f3/schemas/repository.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"title": "Repository",
|
||||
"description": "VCS repository relative to a project. The actual content of the repository is found in the sibling 'repository' directory.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Unique name of the repository relative to the project (e.g. vcs or vcs.wiki).",
|
||||
"type": "string"
|
||||
},
|
||||
"vcs": {
|
||||
"description": "The type of the repository, defaults to 'git'",
|
||||
"enum": [
|
||||
"git",
|
||||
"hg",
|
||||
"bazaar",
|
||||
"darcs",
|
||||
"fossil",
|
||||
"svn"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/repository.json",
|
||||
"$$target": "repository.json"
|
||||
}
|
63
f3/schemas/review.json
Normal file
63
f3/schemas/review.json
Normal file
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"title": "Review",
|
||||
"description": "A set of review comments on a pull/merge request.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier of the review.",
|
||||
"type": "string"
|
||||
},
|
||||
"reviewer_id": {
|
||||
"description": "Unique identifier of review author.",
|
||||
"type": "string"
|
||||
},
|
||||
"official": {
|
||||
"description": "True if a positive review counts to reach the required threshold.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"commit_id": {
|
||||
"description": "SHA of the commit targeted by the review.",
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"description": "Cover message of the review.",
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"description": "Creation time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"state": {
|
||||
"description": "State of the review.",
|
||||
"enum": [
|
||||
"PENDING",
|
||||
"APPROVED",
|
||||
"CHANGES_REQUESTED",
|
||||
"COMMENTED"
|
||||
]
|
||||
},
|
||||
"dissmissed": {
|
||||
"description": "True if the review was dismissed.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"stale": {
|
||||
"description": "True if the review is stale because the pull request content changed after it was published.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"reviewer_id",
|
||||
"commit_id",
|
||||
"content",
|
||||
"created_at",
|
||||
"state"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/review.json",
|
||||
"$$target": "review.json"
|
||||
}
|
77
f3/schemas/reviewcomment.json
Normal file
77
f3/schemas/reviewcomment.json
Normal file
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"title": "Review comment",
|
||||
"description": "A comment in the context of a review.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier of the review comment.",
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"description": "The text of the review comment.",
|
||||
"type": "string"
|
||||
},
|
||||
"tree_path": {
|
||||
"description": "The relative path to the file commented on.",
|
||||
"type": "string"
|
||||
},
|
||||
"diff_hunk": {
|
||||
"description": "The hunk commented on.",
|
||||
"type": "string"
|
||||
},
|
||||
"line": {
|
||||
"description": "The line number of the comment relative to the tree_path.",
|
||||
"type": "number"
|
||||
},
|
||||
"lines_count": {
|
||||
"description": "The range of lines that are commented on. If absent it defaults to one and is a single line comment. If specified it must be a positive number. If line is N and lines_count is C, the range of lines commented on is ]N-C,N]. In other words, the range starts lines_count before line, which is the last of the range",
|
||||
"type": "number"
|
||||
},
|
||||
"commit_id": {
|
||||
"description": "The SHA of the tree_path commented on.",
|
||||
"type": "string"
|
||||
},
|
||||
"poster_id": {
|
||||
"description": "Unique identifier of the user who authored the comment.",
|
||||
"type": "string"
|
||||
},
|
||||
"reactions": {
|
||||
"description": "List of reactions.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "reaction.json"
|
||||
}
|
||||
},
|
||||
"created_at": {
|
||||
"description": "Creation time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updated_at": {
|
||||
"description": "Last update time.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"resolver": {
|
||||
"description": "Unique identifier of the user who resolved the comment.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"content",
|
||||
"tree_path",
|
||||
"diff_hunk",
|
||||
"line",
|
||||
"commit_id",
|
||||
"poster_id",
|
||||
"created_at",
|
||||
"updated_at"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/reviewcomment.json",
|
||||
"$$target": "reviewcomment.json"
|
||||
}
|
25
f3/schemas/topic.json
Normal file
25
f3/schemas/topic.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"title": "Topic",
|
||||
"description": "A category associated with a project. There can be multiple topics/categories for a given project.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the category the project belongs to.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"name"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/topic.json",
|
||||
"$$target": "topic.json"
|
||||
}
|
42
f3/schemas/user.json
Normal file
42
f3/schemas/user.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"title": "User",
|
||||
"description": "A forge user.",
|
||||
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"index": {
|
||||
"description": "Unique identifier of the user.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "User readable name of the user.",
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"description": "Mail of the user.",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "Unique name of the user.",
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"description": "Password of the user.",
|
||||
"type": "string"
|
||||
},
|
||||
"admin": {
|
||||
"description": "True if the user has administrative permissions on the forge.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"index",
|
||||
"name",
|
||||
"username"
|
||||
],
|
||||
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://code.forgejo.org/f3/f3-schemas/src/branch/main/user.json",
|
||||
"$$target": "user.json"
|
||||
}
|
21
f3/topic.go
Normal file
21
f3/topic.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
type Topic struct {
|
||||
Common
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (o Topic) Equal(other Topic) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
o.Name == other.Name
|
||||
}
|
||||
|
||||
func (o *Topic) Clone() Interface {
|
||||
clone := &Topic{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
32
f3/user.go
Normal file
32
f3/user.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package f3
|
||||
|
||||
type User struct {
|
||||
Common
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
UserName string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
IsAdmin bool `json:"admin"`
|
||||
}
|
||||
|
||||
func (o User) Equal(other User) bool {
|
||||
return o.Common.Equal(other.Common) &&
|
||||
o.Name == other.Name &&
|
||||
o.Email == other.Email &&
|
||||
o.UserName == other.UserName &&
|
||||
o.IsAdmin == other.IsAdmin
|
||||
}
|
||||
|
||||
func (o *User) GetName() string {
|
||||
return o.UserName
|
||||
}
|
||||
|
||||
func (o *User) Clone() Interface {
|
||||
clone := &User{}
|
||||
*clone = *o
|
||||
return clone
|
||||
}
|
85
forges/filesystem/asset.go
Normal file
85
forges/filesystem/asset.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
)
|
||||
|
||||
type assetDriver struct {
|
||||
nodeDriver
|
||||
}
|
||||
|
||||
func newAssetDriver(content f3.Interface) generic.NodeDriverInterface {
|
||||
n := newNodeDriver(content).(*nodeDriver)
|
||||
a := &assetDriver{
|
||||
nodeDriver: *n,
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (o *assetDriver) getPath() string {
|
||||
f := o.nodeDriver.content.(*f3.ReleaseAsset)
|
||||
options := o.GetTreeDriver().(*treeDriver).options
|
||||
return filepath.Join(options.Directory, "objects", f.SHA256[0:2], f.SHA256[2:4], f.SHA256)
|
||||
}
|
||||
|
||||
func (o *assetDriver) save(ctx context.Context) {
|
||||
assetFormat := o.nodeDriver.content.(*f3.ReleaseAsset)
|
||||
objectsHelper := o.getF3Tree().GetObjectsHelper()
|
||||
sha, tmpPath := objectsHelper.Save(assetFormat.DownloadFunc())
|
||||
assetFormat.SHA256 = sha
|
||||
path := o.getPath()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.Rename(tmpPath, path); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
o.GetNode().Trace("%s %s", assetFormat.SHA256, path)
|
||||
}
|
||||
|
||||
func (o *assetDriver) setDownloadFunc() {
|
||||
f := o.nodeDriver.content.(*f3.ReleaseAsset)
|
||||
f.DownloadFunc = func() io.ReadCloser {
|
||||
f, err := os.Open(o.getPath())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
func (o *assetDriver) Put(ctx context.Context) id.NodeID {
|
||||
return o.upsert(ctx)
|
||||
}
|
||||
|
||||
func (o *assetDriver) Patch(ctx context.Context) {
|
||||
o.upsert(ctx)
|
||||
}
|
||||
|
||||
func (o *assetDriver) upsert(ctx context.Context) id.NodeID {
|
||||
assetFormat := o.nodeDriver.content.(*f3.ReleaseAsset)
|
||||
if assetFormat.SHA256 != "" {
|
||||
o.save(ctx)
|
||||
}
|
||||
o.setDownloadFunc()
|
||||
return o.nodeDriver.upsert(ctx)
|
||||
}
|
||||
|
||||
func (o *assetDriver) Get(ctx context.Context) bool {
|
||||
if o.nodeDriver.Get(ctx) {
|
||||
o.setDownloadFunc()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
37
forges/filesystem/json.go
Normal file
37
forges/filesystem/json.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func loadJSON(filename string, data any) {
|
||||
bs, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("ReadFile %w", err))
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(bs, data); err != nil {
|
||||
panic(fmt.Errorf("Unmarshal %s %s %w", filename, string(bs), err))
|
||||
}
|
||||
}
|
||||
|
||||
func saveJSON(filename string, data any) {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Create %w", err))
|
||||
}
|
||||
defer f.Close()
|
||||
bs, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("MarshalIndent %w", err))
|
||||
}
|
||||
if _, err := f.Write(bs); err != nil {
|
||||
panic(fmt.Errorf("Write %w", err))
|
||||
}
|
||||
}
|
16
forges/filesystem/main.go
Normal file
16
forges/filesystem/main.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
|
||||
"code.forgejo.org/f3/gof3/v3/options"
|
||||
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
f3_tree.RegisterForgeFactory(filesystem_options.Name, newTreeDriver)
|
||||
options.RegisterFactory(filesystem_options.Name, newOptions)
|
||||
}
|
249
forges/filesystem/node.go
Normal file
249
forges/filesystem/node.go
Normal file
|
@ -0,0 +1,249 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
"code.forgejo.org/f3/gof3/v3/util"
|
||||
)
|
||||
|
||||
type nodeDriver struct {
|
||||
generic.NullDriver
|
||||
|
||||
content f3.Interface
|
||||
}
|
||||
|
||||
func newNodeDriver(content f3.Interface) generic.NodeDriverInterface {
|
||||
return &nodeDriver{
|
||||
content: content.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *nodeDriver) SetNative(any) {}
|
||||
|
||||
func (o *nodeDriver) GetNativeID() string {
|
||||
return o.GetNode().GetID().String()
|
||||
}
|
||||
|
||||
func (o *nodeDriver) getBasePath() string {
|
||||
options := o.GetTreeDriver().(*treeDriver).options
|
||||
return options.Directory + o.GetNode().GetCurrentPath().String()
|
||||
}
|
||||
|
||||
func (o *nodeDriver) getTree() generic.TreeInterface {
|
||||
return o.GetNode().GetTree()
|
||||
}
|
||||
|
||||
func (o *nodeDriver) getF3Tree() f3_tree.TreeInterface {
|
||||
return o.getTree().(f3_tree.TreeInterface)
|
||||
}
|
||||
|
||||
func (o *nodeDriver) isContainer() bool {
|
||||
return o.getF3Tree().IsContainer(o.getKind())
|
||||
}
|
||||
|
||||
func (o *nodeDriver) getKind() kind.Kind {
|
||||
return o.GetNode().GetKind()
|
||||
}
|
||||
|
||||
func (o *nodeDriver) IsNull() bool { return false }
|
||||
|
||||
func (o *nodeDriver) GetIDFromName(ctx context.Context, name string) id.NodeID {
|
||||
switch o.getKind() {
|
||||
case kind.KindRoot, f3_tree.KindProjects, f3_tree.KindUsers, f3_tree.KindOrganizations, f3_tree.KindRepositories:
|
||||
default:
|
||||
panic(fmt.Errorf("unxpected kind %s", o.getKind()))
|
||||
}
|
||||
for _, child := range o.GetNode().List(ctx) {
|
||||
child.Get(ctx)
|
||||
if child.ToFormat().GetName() == name {
|
||||
return child.GetID()
|
||||
}
|
||||
}
|
||||
return id.NilID
|
||||
}
|
||||
|
||||
func (o *nodeDriver) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
|
||||
node := o.GetNode()
|
||||
node.Trace("%s '%s'", o.getKind(), node.GetID())
|
||||
children := generic.NewChildrenSlice(0)
|
||||
|
||||
if o.getKind() == kind.KindRoot || page > 1 {
|
||||
return children
|
||||
}
|
||||
basePath := o.getBasePath()
|
||||
if !util.FileExists(basePath) {
|
||||
return children
|
||||
}
|
||||
|
||||
f3Tree := o.getF3Tree()
|
||||
if !f3Tree.IsContainer(o.getKind()) {
|
||||
return children
|
||||
}
|
||||
|
||||
node.Trace("%d '%s'", page, basePath)
|
||||
|
||||
dirEntries, err := os.ReadDir(basePath)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("ReadDir %s %w", basePath, err))
|
||||
}
|
||||
|
||||
for _, dirEntry := range dirEntries {
|
||||
if !strings.HasSuffix(dirEntry.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
node.Trace(" add %s", dirEntry.Name())
|
||||
child := node.CreateChild(ctx)
|
||||
i := strings.TrimSuffix(dirEntry.Name(), ".json")
|
||||
childID := id.NewNodeID(i)
|
||||
child.SetID(childID)
|
||||
children = append(children, child)
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
func (o *nodeDriver) Equals(context.Context, generic.NodeInterface) bool { panic("") }
|
||||
|
||||
func (o *nodeDriver) LookupMappedID(id id.NodeID) id.NodeID {
|
||||
o.GetNode().Trace("%s", id)
|
||||
return id
|
||||
}
|
||||
|
||||
func (o *nodeDriver) hasJSON() bool {
|
||||
kind := o.getKind()
|
||||
if kind == f3_tree.KindForge {
|
||||
return true
|
||||
}
|
||||
return !o.isContainer()
|
||||
}
|
||||
|
||||
func (o *nodeDriver) Get(context.Context) bool {
|
||||
o.GetNode().Trace("'%s' '%s'", o.getKind(), o.GetNode().GetID())
|
||||
if !o.hasJSON() || o.GetNode().GetID() == id.NilID {
|
||||
return true
|
||||
}
|
||||
filename := o.getBasePath() + ".json"
|
||||
o.GetNode().Trace("'%s'", filename)
|
||||
if !util.FileExists(filename) {
|
||||
return false
|
||||
}
|
||||
f := o.NewFormat()
|
||||
loadJSON(filename, f)
|
||||
o.content = f
|
||||
o.GetNode().Trace("%s %s id=%s", o.getKind(), filename, o.content.GetID())
|
||||
|
||||
idFilename := o.getBasePath() + ".id"
|
||||
if !util.FileExists(idFilename) {
|
||||
return true
|
||||
}
|
||||
mappedID, err := os.ReadFile(idFilename)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Get %s %w", idFilename, err))
|
||||
}
|
||||
o.NullDriver.SetMappedID(id.NewNodeID(string(mappedID)))
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *nodeDriver) SetMappedID(mapped id.NodeID) {
|
||||
o.NullDriver.SetMappedID(mapped)
|
||||
o.saveMappedID()
|
||||
}
|
||||
|
||||
func (o *nodeDriver) saveMappedID() {
|
||||
k := o.getKind()
|
||||
switch k {
|
||||
case kind.KindRoot, f3_tree.KindForge:
|
||||
return
|
||||
}
|
||||
if o.isContainer() {
|
||||
return
|
||||
}
|
||||
mappedID := o.GetMappedID()
|
||||
if mappedID == id.NilID {
|
||||
return
|
||||
}
|
||||
basePath := o.getBasePath()
|
||||
idFilename := basePath + ".id"
|
||||
o.Trace("%s", idFilename)
|
||||
if err := os.WriteFile(idFilename, []byte(o.GetMappedID().String()), 0o644); err != nil {
|
||||
panic(fmt.Errorf("%s %w", idFilename, err))
|
||||
}
|
||||
}
|
||||
|
||||
func (o *nodeDriver) Put(ctx context.Context) id.NodeID {
|
||||
return o.upsert(ctx)
|
||||
}
|
||||
|
||||
func (o *nodeDriver) Patch(ctx context.Context) {
|
||||
o.upsert(ctx)
|
||||
}
|
||||
|
||||
func (o *nodeDriver) upsert(context.Context) id.NodeID {
|
||||
i := o.GetNode().GetID()
|
||||
o.GetNode().Trace("%s %s", o.getKind(), i)
|
||||
o.content.SetID(i.String())
|
||||
if !o.hasJSON() || i == id.NilID {
|
||||
return i
|
||||
}
|
||||
basePath := o.getBasePath()
|
||||
dirname := filepath.Dir(basePath)
|
||||
if !util.FileExists(dirname) {
|
||||
if err := os.MkdirAll(dirname, 0o777); err != nil {
|
||||
panic(fmt.Errorf("MakeDirAll %s %w", dirname, err))
|
||||
}
|
||||
}
|
||||
saveJSON(basePath+".json", o.content)
|
||||
o.saveMappedID()
|
||||
return i
|
||||
}
|
||||
|
||||
func (o *nodeDriver) Delete(context.Context) {
|
||||
if o.isContainer() {
|
||||
return
|
||||
}
|
||||
basePath := o.getBasePath()
|
||||
if util.FileExists(basePath) {
|
||||
if err := os.RemoveAll(basePath); err != nil {
|
||||
panic(fmt.Errorf("RemoveAll %s %w", basePath, err))
|
||||
}
|
||||
}
|
||||
|
||||
for _, ext := range []string{".id", ".json"} {
|
||||
jsonFilename := basePath + ext
|
||||
if util.FileExists(jsonFilename) {
|
||||
if err := os.Remove(jsonFilename); err != nil {
|
||||
panic(fmt.Errorf("RemoveAll %s %w", basePath, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
o.content = o.NewFormat()
|
||||
}
|
||||
|
||||
func (o *nodeDriver) NewFormat() f3.Interface {
|
||||
return o.getTree().(f3_tree.TreeInterface).NewFormat(o.getKind())
|
||||
}
|
||||
|
||||
func (o *nodeDriver) FromFormat(content f3.Interface) {
|
||||
o.content = content
|
||||
}
|
||||
|
||||
func (o *nodeDriver) ToFormat() f3.Interface {
|
||||
return o.content.Clone()
|
||||
}
|
||||
|
||||
func (o *nodeDriver) String() string {
|
||||
return o.content.GetID()
|
||||
}
|
16
forges/filesystem/options.go
Normal file
16
forges/filesystem/options.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
|
||||
"code.forgejo.org/f3/gof3/v3/options"
|
||||
)
|
||||
|
||||
func newOptions() options.Interface {
|
||||
o := &filesystem_options.Options{}
|
||||
o.SetName(filesystem_options.Name)
|
||||
return o
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue