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