Compare commits

...

28 Commits

Author SHA1 Message Date
Earl Warren c88963261c
F3: Gitea driver and CLI
user, topic, project, label, milestone, repository, pull_request,
release, asset, comment, reaction, review providers

Signed-off-by: Earl Warren <contact@earl-warren.org>
2022-11-30 17:23:21 +01:00
Loïc Dachary e302cf89d8
git should ignore GNU/Emacs backups
Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-28 14:32:13 +01:00
Loïc Dachary a21a8d1863 [DOCS] document the forgejo-federation feature branch
Also group branches into categories as the F3 branch is motivated
because it contributes to federation.

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-28 10:46:06 +00:00
oliverpool 1681e37fbb split contributing documentation based on audience 2022-11-28 09:11:12 +01:00
Loïc Dachary 5908c21516
[DOCS] complete CONTRIBUTING.md
Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-27 17:12:02 +01:00
Loïc Dachary 8b1c304d82
[DOCS] Forgejo is inspired by Forĝejo not identical
Fixes: https://codeberg.org/forgejo/forgejo/issues/55

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-26 16:05:56 +01:00
Loïc Dachary 89aaf88fa2 [DOCS] the feature-ci branch has commits related to Woodpecker CI
Fixes: https://codeberg.org/forgejo/forgejo/issues/32

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-26 07:33:52 +00:00
Loïc Dachary f37bda8aba
[DOCS] document the {v1.18/,}forgejo-{ci,development} branches
Fixes: https://codeberg.org/forgejo/forgejo/issues/32

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-26 01:51:41 +01:00
Loïc Dachary 95b89180a5
[DOCS] define the Forgejo release number convention
Fixes: https://codeberg.org/forgejo/forgejo/issues/43

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-26 00:20:20 +01:00
Caesar Schinas 7af1457faf
Add Matrix and Fedi links to Readme 2022-11-26 00:16:30 +01:00
Loïc Dachary eb7bb3ea83
CONTRIBUTING.md: merge WORKFLOW.md & RELEASE.md
Having the development documentation in a single file may not be the best structure but it is how Gitea does it and there is value in keeping something familiar to existing contributors.

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-26 00:16:20 +01:00
Otto b8587f8383
Update README as per !21 2022-11-26 00:16:05 +01:00
Otto b262308667
README draft with skeleton 2022-11-26 00:16:00 +01:00
Loïc Dachary e60e5e6907
RELEASE.md: the release GPG key email is now release@forgejo.org
Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-26 00:15:55 +01:00
Loïc Dachary 611b114f1b
RELEASE.md: document the release process
Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-26 00:15:50 +01:00
Loïc Dachary b1a18b845d
RELEASE.md: add email of the release-team user
Fixes: https://codeberg.org/forgejo/forgejo/issues/6

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-26 00:15:43 +01:00
Caesar Schinas ebf577fcf8
Fix incorrect GPG key in issue template 2022-11-26 00:14:27 +01:00
Caesar Schinas ad36290d47
Update issue templates for Forgejo 2022-11-26 00:14:13 +01:00
Gusted a35b97e6b6
Add `--expert` option for subkey creation
- If this isn't specified, the ECC option wouldn't be shown.
2022-11-26 00:14:04 +01:00
Loïc Dachary 2c1f372d68
delete Gitea specific files that need rewriting for Forgejo
Although it would be possible to modify these files, it would create
conflicts when rebasing. Instead, this commit removes them entirely
and another commit can start from scratch, borrowing content from the
original files.

The drawback of this approach is that some content updates from Gitea
that also need updating in Forgejo will have to be copy/pasted
instead of being merged.

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-26 00:13:59 +01:00
Loïc Dachary e32ca83cbe
Release management documentation
Fixes: https://codeberg.org/Forgejo/forgejo/issues/4

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-26 00:13:54 +01:00
Loïc Dachary a99deecbcd
Forgejo development workflow
Fixes: https://codeberg.org/forgejo/forgejo/issues/5

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-26 00:13:49 +01:00
Loïc Dachary d3b008fab9
workaround: deactivate test-sqlite because it is too slow
Refs: https://codeberg.org/forgejo/forgejo/issues/31

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-25 20:09:57 +01:00
Loïc Dachary e2426ca0f2
workaround: Dockerfile for codeberg.org corrupted container images
https://codeberg.org/forgejo/forgejo/issues/26
https://codeberg.org/Codeberg/Community/issues/800

sed -i -e 's|^FROM.*golang.*|FROM codeberg.org/forgejo/golang:1.19-alpine3.16 AS build-env|' Dockerfile
sed -i -e 's|^FROM.*alpine:.*|FROM codeberg.org/forgejo/alpine:3.16.3|' Dockerfile

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-25 20:09:48 +01:00
Loïc Dachary 0c7a1bf11a
implementation: forgejo container images
Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-25 20:09:44 +01:00
Loïc Dachary 446e235742
implementation: Woodpecker based CI
Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-25 20:09:34 +01:00
Loïc Dachary 054925327f
implementation: publish forgejo- binaries instead of gitea-
Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-25 20:09:24 +01:00
Loïc Dachary 6fddbf49f0
upstream: remove unstable test
Fixes: https://codeberg.org/forgejo/forgejo/issues/30

Signed-off-by: Loïc Dachary <loic@dachary.org>
2022-11-25 20:09:11 +01:00
63 changed files with 3509 additions and 1190 deletions

View File

@ -0,0 +1,53 @@
---
name: "Bug Report"
about: "Found something you weren't expecting? Report it here!"
title: "[BUG] "
---
<!--
NOTE: If your issue is a security concern, please email security@forgejo.org (GPG: A4676E79) instead of opening a public issue.
1. Please speak English, as this is the language all maintainers can
speak and write.
2. Please ask questions or troubleshoot configuration/deploy problems
in our Matrix space (https://matrix.to/#/#forgejo:matrix.org).
3. Please make sure you are using the latest release of Forgejo and
take a moment to check that your issue hasn't been reported before.
4. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report.
5. If you are using a proxy or a CDN (e.g. CloudFlare) in front of
Forgejo, please disable the proxy/CDN fully and connect to Forgejo
directly to confirm the issue still persists without those services.
-->
- Forgejo version (or commit ref):
- Git version:
- Operating system:
- Database (use `[x]`):
- [ ] PostgreSQL
- [ ] MySQL
- [ ] MSSQL
- [ ] SQLite
- How are you running Forgejo?
<!--
Please include information on whether you built Forgejo yourself, used one of our downloads, or are using some other package.
Please also tell us how you are running Forgejo, e.g. if it is being run from docker, a command-line, systemd etc.
If you are using a package or systemd tell us what distribution you are using.
-->
## Description
<!-- Please describe the issue you are having as clearly and succinctly as possible. -->
## Logs
<!--
It is really important to provide pertinent logs. We need DEBUG level logs.
Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems
In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of `app.ini`.
Please copy and paste your logs here, with any sensitive information (e.g. API keys) removed/hidden.
-->
## Screenshots
<!-- If this issue involves the Web Interface, please provide one or more screenshots -->

View File

@ -0,0 +1,21 @@
---
name: "Feature Request"
about: "Got an idea for a feature that Forgejo doesn't have yet? Submit it here!"
title: "[FEAT] "
---
<!--
1. Please speak English, as this is the language all maintainers can
speak and write.
2. Please ask questions or troubleshoot configuration/deploy problems
in our Matrix space (https://matrix.to/#/#forgejo:matrix.org).
3. Please make sure you are using the latest release of Forgejo and
take a moment to check that your feature hasn't already been suggested.
-->
## Feature Description
<!-- Please describe the feature you would like to see added as clearly and succinctly as possible. -->
## Screenshots
<!-- If you can, provide screenshots of an implementation on another site, e.g. GitHub. -->

View File

@ -1,42 +0,0 @@
<!-- NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue -->
<!--
1. Please speak English, this is the language all maintainers can speak and write.
2. Please ask questions or configuration/deploy problems on our Discord
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
3. Please take a moment to check that your issue doesn't already exist.
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq)
5. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report.
-->
- Gitea version (or commit ref):
- Git version:
- Operating system:
<!-- Please include information on whether you built gitea yourself, used one of our downloads or are using some other package -->
<!-- Please also tell us how you are running gitea, e.g. if it is being run from docker, a command-line, systemd etc. --->
<!-- If you are using a package or systemd tell us what distribution you are using -->
- Database (use `[x]`):
- [ ] PostgreSQL
- [ ] MySQL
- [ ] MSSQL
- [ ] SQLite
- Can you reproduce the bug at https://try.gitea.io:
- [ ] Yes (provide example URL)
- [ ] No
- Log gist:
<!-- It really is important to provide pertinent logs -->
<!-- Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems -->
<!-- In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini -->
## Description
<!-- If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please
disable the proxy/CDN fully and connect to gitea directly to confirm
the issue still persists without those services. -->
...
## Screenshots
<!-- **If this issue involves the Web Interface, please include a screenshot** -->

View File

@ -0,0 +1,4 @@
<!--
Before submitting a PR, please read the contributing guidelines:
https://codeberg.org/forgejo/forgejo/src/branch/main/CONTRIBUTING.md
-->

2
.github/FUNDING.yml vendored
View File

@ -1,2 +0,0 @@
open_collective: gitea
custom: https://www.bountysource.com/teams/gitea

View File

@ -1,94 +0,0 @@
name: Bug Report
description: Found something you weren't expecting? Report it here!
labels: kind/bug
body:
- type: markdown
attributes:
value: |
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
- type: markdown
attributes:
value: |
1. Please speak English, this is the language all maintainers can speak and write.
2. Please ask questions or configuration/deploy problems on our Discord
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
3. Make sure you are using the latest release and
take a moment to check that your issue hasn't been reported before.
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq)
5. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report.
6. In particular it's really important to provide pertinent logs. You must give us DEBUG level logs.
Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems
In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
- type: textarea
id: description
attributes:
label: Description
description: |
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
If you are using a proxy or a CDN (e.g. Cloudflare) in front of Gitea, please disable the proxy/CDN fully and access Gitea directly to confirm the issue still persists without those services.
- type: input
id: gitea-ver
attributes:
label: Gitea Version
description: Gitea version (or commit reference) of your instance
validations:
required: true
- type: dropdown
id: can-reproduce
attributes:
label: Can you reproduce the bug on the Gitea demo site?
description: |
If so, please provide a URL in the Description field
URL of Gitea demo: https://try.gitea.io
options:
- "Yes"
- "No"
validations:
required: true
- type: markdown
attributes:
value: |
It's really important to provide pertinent logs
Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems
In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
- type: input
id: logs
attributes:
label: Log Gist
description: Please provide a gist URL of your logs, with any sensitive information (e.g. API keys) removed/hidden
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If this issue involves the Web Interface, please provide one or more screenshots
- type: input
id: git-ver
attributes:
label: Git Version
description: The version of git running on the server
- type: input
id: os-ver
attributes:
label: Operating System
description: The operating system you are using to run Gitea
- type: textarea
id: run-info
attributes:
label: How are you running Gitea?
description: |
Please include information on whether you built Gitea yourself, used one of our downloads, are using https://try.gitea.io or are using some other package
Please also tell us how you are running Gitea, e.g. if it is being run from docker, a command-line, systemd etc.
If you are using a package or systemd tell us what distribution you are using
validations:
required: true
- type: dropdown
id: database
attributes:
label: Database
description: What database system are you running?
options:
- PostgreSQL
- MySQL
- MSSQL
- SQLite

View File

@ -1,17 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Security Concern
url: https://tinyurl.com/security-gitea
about: For security concerns, please send a mail to security@gitea.io instead of opening a public issue.
- name: Discord Server
url: https://discord.gg/Gitea
about: Please ask questions and discuss configuration or deployment problems here.
- name: Discourse Forum
url: https://discourse.gitea.io
about: Questions and configuration or deployment problems can also be discussed on our forum.
- name: Frequently Asked Questions
url: https://docs.gitea.io/en-us/faq
about: Please check if your question isn't mentioned here.
- name: Crowdin Translations
url: https://crowdin.com/project/gitea
about: Translations are managed here.

View File

@ -1,24 +0,0 @@
name: Feature Request
description: Got an idea for a feature that Gitea doesn't have currently? Submit your idea here!
labels: ["kind/feature", "kind/proposal"]
body:
- type: markdown
attributes:
value: |
1. Please speak English, this is the language all maintainers can speak and write.
2. Please ask questions or configuration/deploy problems on our Discord
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
3. Please take a moment to check that your feature hasn't already been suggested.
- type: textarea
id: description
attributes:
label: Feature Description
placeholder: |
I think it would be great if Gitea had...
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If you can, provide screenshots of an implementation on another site e.g. GitHub

View File

@ -1,66 +0,0 @@
name: Web Interface Bug Report
description: Something doesn't look quite as it should? Report it here!
labels: ["kind/bug", "kind/ui"]
body:
- type: markdown
attributes:
value: |
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
- type: markdown
attributes:
value: |
1. Please speak English, this is the language all maintainers can speak and write.
2. Please ask questions or configuration/deploy problems on our Discord
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
3. Please take a moment to check that your issue doesn't already exist.
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq)
5. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report.
6. In particular it's really important to provide pertinent logs. If you are certain that this is a javascript
error, show us the javascript console. If the error appears to relate to Gitea the server you must also give us
DEBUG level logs. (See https://docs.gitea.io/en-us/logging-configuration/#debugging-problems)
- type: textarea
id: description
attributes:
label: Description
description: |
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please disable the proxy/CDN fully and connect to gitea directly to confirm the issue still persists without those services.
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: Please provide at least 1 screenshot showing the issue.
validations:
required: true
- type: input
id: gitea-ver
attributes:
label: Gitea Version
description: Gitea version (or commit reference) your instance is running
validations:
required: true
- type: dropdown
id: can-reproduce
attributes:
label: Can you reproduce the bug on the Gitea demo site?
description: |
If so, please provide a URL in the Description field
URL of Gitea demo: https://try.gitea.io
options:
- "Yes"
- "No"
validations:
required: true
- type: input
id: os-ver
attributes:
label: Operating System
description: The operating system you are using to access Gitea
- type: input
id: browser-ver
attributes:
label: Browser Version
description: The browser and version that you are using to access Gitea
validations:
required: true

23
.github/lock.yml vendored
View File

@ -1,23 +0,0 @@
# Configuration for Lock Threads - https://github.com/dessant/lock-threads-app
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 60
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). `false` is disabled
skipCreatedBefore: false
# Issues and pull requests with these labels will be ignored.
exemptLabels: []
# Label to add before locking, such as `outdated`. `false` is disabled
lockLabel: false
# Comment to post before locking.
lockComment: >
This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a new issue for
related bugs and link to relevant comments in this thread.
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: true

View File

@ -1,9 +0,0 @@
<!--
Please check the following:
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for bug fixes.
2. Read contributing guidelines: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md
3. Describe what your pull request does and which issue you're targeting (if any)
-->

54
.github/stale.yml vendored
View File

@ -1,54 +0,0 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 14
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- status/blocked
- kind/security
- lgtm/done
- reviewed/confirmed
- priority/critical
- kind/proposal
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had recent activity.
I am here to help clear issues left open even if solved or waiting for more insight.
This issue will be closed if no further activity occurs during the next 2 weeks.
If the issue is still valid just add a comment to keep it alive.
Thank you for your contributions.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue has been automatically closed because of inactivity.
You can re-open it if needed.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 1
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
pulls:
daysUntilStale: 60
daysUntilClose: 60
markComment: >
This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs during the next 2 months. Thank you
for your contributions.
closeComment: >
This pull request has been automatically closed because of inactivity.
You can re-open it if needed.

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
# GNU/Emacs
*~
# Compiled Object files, Static and Dynamic libs (Shared Objects) # Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o *.o
*.a *.a

View File

@ -97,6 +97,9 @@ issues:
- gosec - gosec
- unparam - unparam
- staticcheck - staticcheck
- path: services/f3/driver/driver.go
linters:
- typecheck
- path: models/migrations/v - path: models/migrations/v
linters: linters:
- gocyclo - gocyclo

View File

@ -0,0 +1,32 @@
platform: linux/amd64
workspace:
base: /go
path: src/codeberg/gitea
pipeline:
deps-backend:
image: golang:1.19
pull: true
commands:
- make deps-backend
security-check:
image: golang:1.19
pull: true
commands:
- make security-check
lint-backend:
image: gitea/test_env:linux-amd64
pull: true
environment:
- TAGS=bindata sqlite sqlite_unlock_notify
- GOSUMDB=sum.golang.org
commands:
- make lint-backend
checks-backend:
image: golang:1.19
commands:
- make --always-make checks-backend

View File

@ -0,0 +1,28 @@
platform: linux/amd64
depends_on:
- testing-amd64
pipeline:
fetch-tags:
image: docker:git
pull: true
commands:
- git config --add safe.directory '*'
- git fetch --tags --force
publish:
image: woodpeckerci/plugin-docker-buildx
pull: true
settings:
platforms: linux/amd64
registry:
from_secret: domain
tag: ${CI_COMMIT_TAG##v}
repo: ${CI_REPO_LINK##https://}
password:
from_secret: releaseteamtoken
username:
from_secret: releaseteamuser
when:
event: tag

View File

@ -0,0 +1,65 @@
platform: linux/amd64
depends_on:
- testing-amd64
workspace:
base: /source
path: /
pipeline:
fetch-tags:
image: docker:git
pull: true
commands:
- git config --add safe.directory '*'
- git fetch --tags --force
deps-frontend:
image: node:18
pull: true
commands:
- make deps-frontend
deps-backend:
image: golang:1.19
pull: true
commands:
- make deps-backend
static:
image: techknowlogick/xgo:go-1.19.x
pull: true
commands:
- curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get -qqy install nodejs
- export PATH=$PATH:$GOPATH/bin
- make CI=true LINUX_ARCHS=linux/amd64,linux/arm64 release
environment:
TAGS: bindata sqlite sqlite_unlock_notify
DEBIAN_FRONTEND: noninteractive
gpg-sign:
image: plugins/gpgsign:1
pull: true
settings:
detach_sign: true
excludes:
- "dist/release/*.sha256"
files:
- "dist/release/*"
key:
from_secret: releaseteamgpg
release:
image: golang:1.19
commands:
- curl -sL https://dl.gitea.io/tea/0.9.0/tea-0.9.0-linux-amd64 > /bin/tea && chmod +x /bin/tea
- REMOTE=$(echo $CI_REPO_LINK | sed -e 's|.*://||' -e 's|/.*||')
- GITEA_SERVER_URL=$CI_REPO_LINK GITEA_SERVER_TOKEN=$RELEASETEAMTOKEN tea login add --name $RELEASETEAMUSER --url $REMOTE
- ASSETS=$(ls dist/release/* | sed -e 's/^/-a /')
- tea release create $ASSETS --tag $CI_COMMIT_TAG --title $CI_COMMIT_TAG
when:
event: tag
secrets:
- releaseteamtoken
- releaseteamuser

View File

@ -0,0 +1,63 @@
platform: linux/amd64
depends_on:
- compliance
workspace:
base: /go
path: src/codeberg/gitea
pipeline:
fetch-tags:
image: docker:git
pull: true
commands:
- git config --add safe.directory '*'
- git fetch --tags --force
deps-backend:
image: golang:1.19
pull: true
commands:
- make deps-backend
tag-pre-condition:
image: drone/git
pull: true
commands:
- git update-ref refs/heads/tag_test ${CI_COMMIT_SHA}
prepare-test-env:
image: gitea/test_env:linux-amd64
pull: true
commands:
- ./build/test-env-prepare.sh
build:
image: gitea/test_env:linux-amd64
environment:
- GOSUMDB=sum.golang.org
- TAGS=bindata sqlite sqlite_unlock_notify
commands:
- su gitea -c './build/test-env-check.sh'
- su gitea -c 'make backend'
unit-test:
image: gitea/test_env:linux-amd64
environment:
- TAGS=bindata sqlite sqlite_unlock_notify
- RACE_ENABLED=true
secrets:
- github_read_token
commands:
- su gitea -c 'make unit-test-coverage test-check'
# test-sqlite:
# image: gitea/test_env:linux-amd64
# environment:
# - USE_REPO_TEST_DIR=1
# - GOPROXY=off
# - TAGS=bindata gogit sqlite sqlite_unlock_notify
# - TEST_TAGS=bindata gogit sqlite sqlite_unlock_notify
# commands:
# - su gitea -c 'timeout -s ABRT 120m make test-sqlite-migration test-sqlite'

View File

@ -1,449 +1,23 @@
# Contribution Guidelines # Forgejo Contributor Guide
## Table of Contents The Forgejo project is run by a community of people who are expected to follow this guide when cooperating on a simple bug fix as well as when changing the governance. For more information about the project, take a look at [the documentation explaining what Forgejo provides](README.md).
- [Contribution Guidelines](#contribution-guidelines) Sensitive security-related issues should be reported to [security@forgejo.org](mailto:security@forgejo.org) using [encryption](https://keyoxide.org/security@forgejo.org).
- [Table of Contents](#table-of-contents)
- [Introduction](#introduction)
- [Bug reports](#bug-reports)
- [Discuss your design](#discuss-your-design)
- [Testing redux](#testing-redux)
- [Vendoring](#vendoring)
- [Translation](#translation)
- [Building Gitea](#building-gitea)
- [Code review](#code-review)
- [Styleguide](#styleguide)
- [Design guideline](#design-guideline)
- [API v1](#api-v1)
- [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco)
- [Release Cycle](#release-cycle)
- [Maintainers](#maintainers)
- [Owners](#owners)
- [Versions](#versions)
- [Releasing Gitea](#releasing-gitea)
- [Copyright](#copyright)
## Introduction ## For everyone involved
This document explains how to contribute changes to the Gitea project. - [Code of Conduct](CONTRIBUTING/COC.md)
It assumes you have followed the - [Bugs, features, security and others discussions](CONTRIBUTING/DISCUSSIONS.md)
[installation instructions](https://docs.gitea.io/en-us/). - [Governance](CONTRIBUTING/GOVERNANCE.md)
Sensitive security-related issues should be reported to - [Funding](CONTRIBUTING/FUNDING.md)
[security@gitea.io](mailto:security@gitea.io).
For configuring IDE or code editor to develop Gitea see [IDE and code editor configuration](contrib/ide/) ## For contributors
## Bug reports - [Developer Certificate of Origin (DCO)](CONTRIBUTING/DCO.md)
- [Development workflow](CONTRIBUTING/WORKFLOW.md)
Please search the issues on the issue tracker with a variety of keywords ## For maintainers
to ensure your bug is not already reported.
If unique, [open an issue](https://github.com/go-gitea/gitea/issues/new) - [Release management](CONTRIBUTING/RELEASE.md)
and answer the questions so we can understand and reproduce the - [Secrets](CONTRIBUTING/SECRETS.md)
problematic behavior.
To show us that the issue you are having is in Gitea itself, please
write clear, concise instructions so we can reproduce the behavior—
even if it seems obvious. The more detailed and specific you are,
the faster we can fix the issue. Check out [How to Report Bugs
Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
Please be kind, remember that Gitea comes at no cost to you, and you're
getting free help.
## Discuss your design
The project welcomes submissions. If you want to change or add something,
please let everyone know what you're working on—[file an issue](https://github.com/go-gitea/gitea/issues/new)!
Significant changes must go through the change proposal process
before they can be accepted. To create a proposal, file an issue with
your proposed changes documented, and make sure to note in the title
of the issue that it is a proposal.
This process gives everyone a chance to validate the design, helps
prevent duplication of effort, and ensures that the idea fits inside
the goals for the project and tools. It also checks that the design is
sound before code is written; the code review tool is not the place for
high-level discussions.
## Testing redux
Before submitting a pull request, run all the tests for the whole tree
to make sure your changes don't cause regression elsewhere.
Here's how to run the test suite:
- code lint
| | |
| :-------------------- | :---------------------------------------------------------------- |
|``make lint`` | lint everything (not suggest if you only change one type code) |
|``make lint-frontend`` | lint frontend files |
|``make lint-backend`` | lint backend files |
- run test code (Suggest run in Linux)
| | |
| :------------------------------------- | :----------------------------------------------- |
|``make test[\#TestSpecificName]`` | run unit test |
|``make test-sqlite[\#TestSpecificName]``| run [integration](tests/integration) test for SQLite |
|[More details about integration tests](tests/integration/README.md) |
|``make test-e2e-sqlite[\#TestSpecificFileName]``| run [end-to-end](tests/e2e) test for SQLite |
|[More details about e2e tests](tests/e2e/README.md) |
## Vendoring
We manage dependencies via [Go Modules](https://golang.org/cmd/go/#hdr-Module_maintenance), more details: [go mod](https://go.dev/ref/mod).
Pull requests should only include `go.mod`, `go.sum` updates if they are part of
the same change, be it a bugfix or a feature addition.
The `go.mod`, `go.sum` update needs to be justified as part of the PR description,
and must be verified by the reviewers and/or merger to always reference
an existing upstream commit.
You can find more information on how to get started with it on the [Modules Wiki](https://github.com/golang/go/wiki/Modules).
## Translation
We do all translation work inside [Crowdin](https://crowdin.com/project/gitea).
The only translation that is maintained in this Git repository is
[`en_US.ini`](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini)
and is synced regularly to Crowdin. Once a translation has reached
A SATISFACTORY PERCENTAGE it will be synced back into this repo and
included in the next released version.
## Building Gitea
See the [hacking instructions](https://docs.gitea.io/en-us/hacking-on-gitea/).
## Code review
Changes to Gitea must be reviewed before they are accepted—no matter who
makes the change, even if they are an owner or a maintainer. We use GitHub's
pull request workflow to do that. And, we also use [LGTM](http://lgtm.co)
to ensure every PR is reviewed by at least 2 maintainers.
Please try to make your pull request easy to review for us. And, please read
the *[How to get faster PR reviews](https://github.com/kubernetes/community/blob/261cb0fd089b64002c91e8eddceebf032462ccd6/contributors/guide/pull-requests.md#best-practices-for-faster-reviews)* guide;
it has lots of useful tips for any project you may want to contribute.
Some of the key points:
- Make small pull requests. The smaller, the faster to review and the
more likely it will be merged soon.
- Don't make changes unrelated to your PR. Maybe there are typos on
some comments, maybe refactoring would be welcome on a function... but
if that is not related to your PR, please make *another* PR for that.
- Split big pull requests into multiple small ones. An incremental change
will be faster to review than a huge PR.
- Use the first comment as a summary explainer of your PR and you should keep this up-to-date as the PR evolves.
If your PR could cause a breaking change you must add a BREAKING section to this comment e.g.:
```
## :warning: BREAKING :warning:
```
To explain how this could affect users and how to mitigate these changes.
Once code review starts on your PR, do not rebase nor squash your branch as it makes it
difficult to review the new changes. Only if there is a need, sync your branch by merging
the base branch into yours. Don't worry about merge commits messing up your tree as
the final merge process squashes all commits into one, with the visible commit message (first
line) being the PR title + PR index and description being the PR's first comment.
Once your PR gets the `lgtm/done` label, don't worry about keeping it up-to-date or breaking
builds (unless there's a merge conflict or a request is made by a maintainer to make
modifications). It is the maintainer team's responsibility from this point to get it merged.
## Styleguide
For imports you should use the following format (*without* the comments)
```go
import (
// stdlib
"fmt"
"math"
// local packages
"code.gitea.io/gitea/models"
"code.gitea.io/sdk/gitea"
// external packages
"github.com/foo/bar"
"gopkg.io/baz.v1"
)
```
## Design guideline
To maintain understandable code and avoid circular dependencies it is important to have a good structure of the code. The Gitea code is divided into the following parts:
- **models:** Contains the data structures used by xorm to construct database tables. It also contains supporting functions to query and update the database. Dependencies to other code in Gitea should be avoided although some modules might be needed (for example for logging).
- **models/fixtures:** Sample model data used in integration tests.
- **models/migrations:** Handling of database migrations between versions. PRs that changes a database structure shall also have a migration step.
- **modules:** Different modules to handle specific functionality in Gitea. Shall only depend on other modules but not other packages (models, services).
- **public:** Frontend files (javascript, images, css, etc.)
- **routers:** Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) shall not depend on routers.
- **services:** Support functions for common routing operations. Uses models and modules to handle the request.
- **templates:** Golang templates for generating the html output.
- **tests/e2e:** End to end tests
- **tests/integration:** Integration tests
- **vendor:** External code that Gitea depends on.
## Documentation
If you add a new feature or change an existing aspect of Gitea, the documentation for that feature must be created or updated.
## API v1
The API is documented by [swagger](http://try.gitea.io/api/swagger) and is based on [GitHub API v3](https://developer.github.com/v3/).
Thus, Gitea´s API should use the same endpoints and fields as GitHub´s API as far as possible, unless there are good reasons to deviate.
If Gitea provides functionality that GitHub does not, a new endpoint can be created.
If information is provided by Gitea that is not provided by the GitHub API, a new field can be used that doesn't collide with any GitHub fields.
Updating an existing API should not remove existing fields unless there is a really good reason to do so.
The same applies to status responses. If you notice a problem, feel free to leave a comment in the code for future refactoring to APIv2 (which is currently not planned).
All expected results (errors, success, fail messages) should be documented
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L319-L327)).
All JSON input types must be defined as a struct in [modules/structs/](modules/structs/)
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L76-L91))
and referenced in
[routers/api/v1/swagger/options.go](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/options.go).
They can then be used like the following:
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L318)).
All JSON responses must be defined as a struct in [modules/structs/](modules/structs/)
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L36-L68))
and referenced in its category in [routers/api/v1/swagger/](routers/api/v1/swagger/)
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/issue.go#L11-L16))
They can be used like the following:
([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L277-L279))
In general, HTTP methods are chosen as follows:
- **GET** endpoints return requested object and status **OK (200)**
- **DELETE** endpoints return status **No Content (204)**
- **POST** endpoints return status **Created (201)**, used to **create** new objects (e.g. a User)
- **PUT** endpoints return status **No Content (204)**, used to **add/assign** existing Objects (e.g. User) to something (e.g. Org-Team)
- **PATCH** endpoints return changed object and status **OK (200)**, used to **edit/change** an existing object
An endpoint which changes/edits an object expects all fields to be optional (except ones to identify the object, which are required).
### Endpoints returning lists should
- support pagination (`page` & `limit` options in query)
- set `X-Total-Count` header via **SetTotalCountHeader** ([example](https://github.com/go-gitea/gitea/blob/7aae98cc5d4113f1e9918b7ee7dd09f67c189e3e/routers/api/v1/repo/issue.go#L444))
## Backports and Frontports
Occasionally backports of PRs are required.
The backported PR title should be:
```
Title of backported PR (#ORIGINAL_PR_NUMBER)
```
The first two lines of the summary of the backporting PR should be:
```
Backport #ORIGINAL_PR_NUMBER
```
with the rest of the summary matching the original PR. Similarly for frontports
---
The below is a script that may be helpful in creating backports. YMMV.
```bash
#!/bin/sh
PR="$1"
SHA="$2"
VERSION="$3"
if [ -z "$SHA" ]; then
SHA=$(gh api /repos/go-gitea/gitea/pulls/$PR -q '.merge_commit_sha')
fi
if [ -z "$VERSION" ]; then
VERSION="v1.16"
fi
echo git checkout origin/release/"$VERSION" -b backport-$PR-$VERSION
git checkout origin/release/"$VERSION" -b backport-$PR-$VERSION
git cherry-pick $SHA && git commit --amend && git push zeripath backport-$PR-$VERSION && xdg-open https://github.com/go-gitea/gitea/compare/release/"$VERSION"...zeripath:backport-$PR-$VERSION
```
## Developer Certificate of Origin (DCO)
We consider the act of contributing to the code by submitting a Pull
Request as the "Sign off" or agreement to the certifications and terms
of the [DCO](DCO) and [MIT license](LICENSE). No further action is required.
Additionally you could add a line at the end of your commit message.
```
Signed-off-by: Joe Smith <joe.smith@email.com>
```
If you set your `user.name` and `user.email` Git configs, you can add the
line to the end of your commit automatically with `git commit -s`.
We assume in good faith that the information you provide is legally binding.
## Release Cycle
We adopted a release schedule to streamline the process of working
on, finishing, and issuing releases. The overall goal is to make a
minor release every three or four months, which breaks down into two or three months of
general development followed by one month of testing and polishing
known as the release freeze. All the feature pull requests should be
merged before feature freeze. And, during the frozen period, a corresponding
release branch is open for fixes backported from main branch. Release candidates
are made during this period for user testing to
obtain a final version that is maintained in this branch. A release is
maintained by issuing patch releases to only correct critical problems
such as crashes or security issues.
Major release cycles are seasonal. They always begin on the 25th and end on
the 24th (i.e., the 25th of December to March 24th).
During a development cycle, we may also publish any necessary minor releases
for the previous version. For example, if the latest, published release is
v1.2, then minor changes for the previous release—e.g., v1.1.0 -> v1.1.1—are
still possible.
## Maintainers
To make sure every PR is checked, we have [team
maintainers](MAINTAINERS). Every PR **MUST** be reviewed by at least
two maintainers (or owners) before it can get merged. A maintainer
should be a contributor of Gitea (or Gogs) and contributed at least
4 accepted PRs. A contributor should apply as a maintainer in the
[Discord](https://discord.gg/NsatcWJ) #develop channel. The owners
or the team maintainers may invite the contributor. A maintainer
should spend some time on code reviews. If a maintainer has no
time to do that, they should apply to leave the maintainers team
and we will give them the honor of being a member of the [advisors
team](https://github.com/orgs/go-gitea/teams/advisors). Of course, if
an advisor has time to code review, we will gladly welcome them back
to the maintainers team. If a maintainer is inactive for more than 3
months and forgets to leave the maintainers team, the owners may move
him or her from the maintainers team to the advisors team.
For security reasons, Maintainers should use 2FA for their accounts and
if possible provide GPG signed commits.
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
https://help.github.com/articles/signing-commits-with-gpg/
## Owners
Since Gitea is a pure community organization without any company support,
to keep the development healthy we will elect three owners every year. All
contributors may vote to elect up to three candidates, one of which will
be the main owner, and the other two the assistant owners. When the new
owners have been elected, the old owners will give up ownership to the
newly elected owners. If an owner is unable to do so, the other owners
will assist in ceding ownership to the newly elected owners.
For security reasons, Owners or any account with write access (like a bot)
must use 2FA.
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
After the election, the new owners should proactively agree
with our [CONTRIBUTING](CONTRIBUTING.md) requirements in the
[Discord](https://discord.gg/NsatcWJ) #general channel. Below are the
words to speak:
```
I'm honored to having been elected an owner of Gitea, I agree with
[CONTRIBUTING](CONTRIBUTING.md). I will spend part of my time on Gitea
and lead the development of Gitea.
```
To honor the past owners, here's the history of the owners and the time
they served:
- 2022-01-01 ~ 2022-12-31 - https://github.com/go-gitea/gitea/issues/17872
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.io>
- [Andrew Thornton](https://gitea.com/zeripath) <art27@cantab.net>
- 2021-01-01 ~ 2021-12-31 - https://github.com/go-gitea/gitea/issues/13801
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
- [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) <lauris@nix.lv>
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.io>
- 2020-01-01 ~ 2020-12-31 - https://github.com/go-gitea/gitea/issues/9230
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
- [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) <lauris@nix.lv>
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.io>
- 2019-01-01 ~ 2019-12-31 - https://github.com/go-gitea/gitea/issues/5572
- [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
- [Lauris Bukšis-Haberkorns](https://github.com/lafriks) <lauris@nix.lv>
- [Matti Ranta](https://github.com/techknowlogick) <techknowlogick@gitea.io>
- 2018-01-01 ~ 2018-12-31 - https://github.com/go-gitea/gitea/issues/3255
- [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
- [Lauris Bukšis-Haberkorns](https://github.com/lafriks) <lauris@nix.lv>
- [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
- 2016-11-04 ~ 2017-12-31
- [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
- [Thomas Boerger](https://github.com/tboerger) <thomas@webhippie.de>
- [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
## Versions
Gitea has the `main` branch as a tip branch and has version branches
such as `release/v0.9`. `release/v0.9` is a release branch and we will
tag `v0.9.0` for binary download. If `v0.9.0` has bugs, we will accept
pull requests on the `release/v0.9` branch and publish a `v0.9.1` tag,
after bringing the bug fix also to the main branch.
Since the `main` branch is a tip version, if you wish to use Gitea
in production, please download the latest release tag version. All the
branches will be protected via GitHub, all the PRs to every branch must
be reviewed by two maintainers and must pass the automatic tests.
## Releasing Gitea
- Let $vmaj, $vmin and $vpat be Major, Minor and Patch version numbers, $vpat should be rc1, rc2, 0, 1, ...... $vmaj.$vmin will be kept the same as milestones on github or gitea in future.
- Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on Discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody against in about serval hours.
- If this is a big version first you have to create PR for changelog on branch `main` with PRs with label `changelog` and after it has been merged do following steps:
- Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`.
- When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin`
- If it is bugfix version create PR for changelog on branch `release/v$vmaj.$vmin` and wait till it is reviewed and merged.
- Add a tag as `git tag -s -F release.notes v$vmaj.$vmin.$`, release.notes file could be a temporary file to only include the changelog this version which you added to `CHANGELOG.md`.
- And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically create a release and upload all the compiled binary. (But currently it doesn't add the release notes automatically. Maybe we should fix that.)
- If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version.
- Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
- Verify all release assets were correctly published through CI on dl.gitea.io and GitHub releases. Once ACKed:
- bump the version of https://dl.gitea.io/gitea/version.json
- merge the blog post PR
- announce the release in discord `#announcements`
## Copyright
Code that you contribute should use the standard copyright header:
```
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
```
Files in the repository contain copyright from the year they are added
to the year they are last changed. If the copyright author is changed,
just paste the header below the old one.

26
CONTRIBUTING/COC.md Normal file
View File

@ -0,0 +1,26 @@
# Code of Conduct, Well Being and Moderation teams
Forgejo strives to be an inclusive project where everyone can participate in a safe environment. The [Well Being](https://codeberg.org/org/forgejo/teams/well-being) team is doing its best to defuse tensions before they escalate and is available to answer all requests sent its way. When diplomacy fails, the [Moderation](https://codeberg.org/org/forgejo/teams/moderation) will be forced to act to put a stop to actions that are contrary to the [Code of Conduct](https://codeberg.org/forgejo/code-of-conduct).
## Well Being and Moderation teams
Temporary Well Being and Moderation teams [were appointed 10 November 2022](https://codeberg.org/forgejo/meta/issues/13).
The moderation team will rely on this [Code of Conduct](https://codeberg.org/forgejo/code-of-conduct) when diplomacy fails.
### [Well Being](https://codeberg.org/org/forgejo/teams/well-being)
Their goal is to defuse tensions.
It has no power whatsover. The members are approved by the organization and trusted to:
- Read all communications to detect tensions between people before they escalate.
- Do their best to defuse tensions.
### [Moderation](https://codeberg.org/org/forgejo/teams/moderation)
Their goal is to enforce the [Code of Conduct](https://codeberg.org/forgejo/code-of-conduct) when diplomacy fails.
It has the power to exclude people from a space.
Their decisions must be logical, fact based and transparent to the organization trusting them with the task.

29
CONTRIBUTING/DCO.md Normal file
View File

@ -0,0 +1,29 @@
# Developer Certificate of Origin (DCO)
Contributions to Forgejo, in all the repositories in the [Forgejo organization](https://codeberg.org/forgejo) are accepted provided the author agrees to the following Developer Certificate of Origin (DCO).
```
By making a contribution to Forgejo, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the Free Software
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate Free Software
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same Free Software license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the Free Software license(s) involved.
```

View File

@ -0,0 +1,18 @@
# Bugs, features and discussions
The [Forgejo issue tracker](https://codeberg.org/forgejo/forgejo/issues) is where **bugs** should be reported and **features** requested.
Dedicated repositories in the [Forgejo organization](https://codeberg.org/forgejo) cover areas such as:
- the [website](https://codeberg.org/forgejo/website)
- the [Code of Conduct](https://codeberg.org/forgejo/code-of-conduct)
- the [funding](https://codeberg.org/forgejo/funding).
Other discussions regarding all **non technical aspects** of Forgejo, such as the governance, happen in the [meta issue tracker](https://codeberg.org/forgejo/meta/issues) and in the [matrix chatroom](https://matrix.to/#/#forgejo-chat:matrix.org).
# Security
The [security team](https://codeberg.org/org/forgejo/teams/security) handle security vulnerabilities. It handles sensitive security-related issues reported to [security@forgejo.org](mailto:security@forgejo.org) using [encryption](https://keyoxide.org/security@forgejo.org).
The security team also keeps the content of the [security.txt](https://codeberg.org/forgejo/website/src/branch/main/public/.well-known/security.txt) file up to date.
The private GPG key for `security@forgejo.org` is shared among all members of the security team and not stored online.

21
CONTRIBUTING/FUNDING.md Normal file
View File

@ -0,0 +1,21 @@
# Funding per year
## 2022
* 50,000€ [to further forge federation](https://forum.forgefriends.org/t/nlnet-grant-application-for-federation-in-gitea-deadline-august-1st-2022/823)
* 10,000€ (1,400€ October, 5,600€ November, 2,800€ December) employee delegation from Easter-eggs (Loïc Dachary)
# Funding ideas and prospects
## Grant applications
* [Forgejo NLnet December 1st 2022](https://codeberg.org/forgejo/funding/issues/1)
* [UX/UI NLnet December 1st 2022](https://codeberg.org/forgejo/funding/issues/4)
## Discussions
* [Proposition](https://codeberg.org/forgejo/meta/issues/51) to log volunteers time so that it is accounted for at a 60€ per hour rate.
* [A solution for sustaining Free Software forge development](https://blog.dachary.org/2022/11/05/a-solution-for-sustaining-forge-development/)
* A French company delegates an employee for X month
* The employee's time is paid for by [R&D incentive](https://fr.wikipedia.org/wiki/Cr%C3%A9dit_d%27imp%C3%B4t_recherche)
* [Donations to Codeberg](https://codeberg.org/forgejo/meta/issues/26)

View File

@ -0,0 +1,21 @@
# Governance
## Codeberg e.V. stewards of the domains
The Forgejo [domains](https://codeberg.org/forgejo/meta/issues/41) are owned by the democratic non-profit dedicated to Free Software [Codeberg e.V.](https://codeberg.org/Codeberg/org/src/branch/main/en/bylaws.md). Forgejo is therefore ultimately under the control of Codeberg e.V. and its governance. However, although Codeberg e.V. is committed to use and host Forgejo, it is expected that Forgejo defines its own governance, in a way that is compatible with the Codeberg e.V. governance.
## Interim Forgejo Governance
Although Codeberg e.V. guarantees Forgejo is ultimately under the control of a trusted organization, there was a need to establish an interim Forgejo governance for safeguarding credentials, enforcing the Code of Conduct and ensuring security vulnerabilities are handled responsibly for the Forgejo releases.
All people with a role in the interim Forgejo governance pledge to resign as soon as the Forgejo governance is in place.
* [release managers](https://codeberg.org/org/forgejo/teams/releases) safeguard the keys used to sign Forgejo releases
* [secret keepers](https://codeberg.org/org/Forgejo/teams/owners) safeguard all credentials (registrar, social accounts, etc.)
* [security team](https://codeberg.org/org/forgejo/teams/security) handle security vulnerabilities
* [Well Being](https://codeberg.org/org/forgejo/teams/well-being) and [Moderation](https://codeberg.org/org/forgejo/teams/moderation) teams help keep Forgejo an inclusive space
## Forgejo Governance
Forgejo was bootstraped in November 2022 and is [defining its governance](https://codeberg.org/forgejo/meta/issues/19). The [first meeting happened November 24th](https://codeberg.org/forgejo/meta/issues/19#issuecomment-694460) and was recorded. Everyone is welcome to participate in this fully transparent and cooperative process.

62
CONTRIBUTING/RELEASE.md Normal file
View File

@ -0,0 +1,62 @@
# Release management
## Shared user: release-team
The [release-team](https://codeberg.org/release-team) user publishes and signs all releases. The associated email is mailto:release@forgejo.org.
The public GPG key used to sign the releases is [EB114F5E6C0DC2BCDD183550A4B61A2DC5923710](https://codeberg.org/release-team.gpg) `Forgejo Releases <release@forgejo.org>`
## Release numbering
The Forgejo release numbers are composed of the Gitea release number followed by a dash and a serial number. For instance:
* Gitea **v1.18.0** will be Forgejo **v1.18.0-0**, **v1.18.0-1**, etc
The Gitea release candidates are suffixed with **-rcN** which is handled as a special case for packaging: although **X.Y.Z** is lexicographically lower than **X.Y.Z-rc1** is is considered greater. The Forgejo serial number must therefore be inserted before the **-rcN** suffix to preserve the expected version ordering.
* Gitea **v1.18.0-rc0** will be Forgejo **v1.18.0-0-rc0**, **v1.18.0-1-rc0**
* Gitea **v1.18.0-rc1** will be Forgejo **v1.18.0-2-rc1**, **v1.18.0-3-rc1**, **v1.18.0-4-rc1**
* Gitea **v1.18.0** will be Forgejo **v1.18.0-5**, **v1.18.0-6**, **v1.18.0-7**
* etc.
## Release process
* Reset the vX.Y/forgejo-integration branch to the Gitea tag vX.Y.Z
* Merge all feature branches into the vX.Y/forgejo-integration branch
* If the CI passes reset the vX.Y/forgejo branch to the tip of vX.Y/forgejo-integration
* Set the vX.Y.Z-N tag to the tip of the vX.Y/forgejo branch
* [Binaries](https://codeberg.org/forgejo/forgejo/releases) are built, signed and uploaded by the CI.
* [Container images](https://codeberg.org/forgejo/-/packages/container/forgejo/versions) are built and uploaded by the CI.
## Release signing keys management
A GPG master key with no expiration date is created and shared with members of the Owners team via encrypted email. A subkey with a one year expiration date is created and stored in the secrets repository, to be used by the CI pipeline. The public master key is stored in the secrets repository and published where relevant.
### Master key creation
* gpg --expert --full-generate-key
* key type: ECC and ECC option with Curve 25519 as curve
* no expiration
* id: Forgejo Releases <contact@forgejo.org>
* gpg --export-secret-keys --armor EB114F5E6C0DC2BCDD183550A4B61A2DC5923710 and send via encrypted email to Owners
* gpg --export --armor EB114F5E6C0DC2BCDD183550A4B61A2DC5923710 > release-team-gpg.pub
* commit to the secret repository
### Subkey creation and renewal
* gpg --expert --edit-key EB114F5E6C0DC2BCDD183550A4B61A2DC5923710
* addkey
* key type: ECC (signature only)
* key validity: one year
* create [an issue](https://codeberg.org/forgejo/forgejo/issues) to schedule the renewal
#### 2023
* gpg --export --armor F7CBF02094E7665E17ED6C44E381BF3E50D53707 > 2023-release-team-gpg.pub
* gpg --export-secret-keys --armor F7CBF02094E7665E17ED6C44E381BF3E50D53707 > 2023-release-team-gpg
* commit to the secrets repository
* renewal issue https://codeberg.org/forgejo/forgejo/issues/58
### CI configuration
The `releaseteamgpg` secret in the Woodpecker CI configuration is set with the subkey.

56
CONTRIBUTING/SECRETS.md Normal file
View File

@ -0,0 +1,56 @@
# Secrets
All Forgejo credentials are shared among the [secret keepers](https://codeberg.org/org/Forgejo/teams/owners) teams in a private repository with encrypted content.
## Get started
1. Make sure you have a GPG Key, or [create one](https://github.com/NicoHood/gpgit#12-key-generation)
2. Send someone else your public key and ask this person to add yourself as a recipient
```
# Commands for the other person
$ gpg --import public_key.asc
# The following command will open a prompt, with the available public keys.
# Choose the one you just added and all secrets will be re-encrypted with this new key.
$ gopass recipients add
```
3. [Install gopass](https://www.gopass.pw/#install)
> :warning: When installing on Ubuntu or Debian you can either download the deb package, install manually or build from source or use our APT repository ([github comment](https://github.com/gopasspw/gopass/issues/1849#issuecomment-802789285) with more information).
4. Clone this repo using `gopass` (the name and email are for `git config`)
```
$ gopass clone git@codeberg.org:Forgejo/gopass.git
```
5. Check the consistency of the gopass storage
```
$ gopass fsck
```
## Get a secret
Show the whole secret file:
```
$ gopass show ovh.com/manager
```
Copy the password in the clipboard:
```
$ gopass show -c ovh.com/manager
```
Copy the `user` part of the secret in the clipboard:
```
$ gopass show -c ovh.com/manager user
```
## Insert or edit a secret
```
$ gopass edit ovh.com/manager
```
In the editor, insert the password on the first line.
You may then add lines with a `key: value` syntax (`user: username` for instance).
## Debugging and manual git operations
The following command will show the location and status of the git repo (all git commands are available).
```
$ gopass git status
```

79
CONTRIBUTING/WORKFLOW.md Normal file
View File

@ -0,0 +1,79 @@
# Development workflow
Forgejo is a soft fork, i.e. a set of commits applied to the Gitea development branch and the stable branches. On a regular basis those commits are rebased and modified if necessary to keep working. All Forgejo commits are merged into a branch from which binary releases and packages are created and distributed. The development workflow is a set of conventions Forgejo developers are expected to follow to work together.
Discussions on how the workflow should evolve happen [in the isssue tracker](https://codeberg.org/forgejo/forgejo/issues?type=all&state=open&labels=&milestone=0&assignee=0&q=%5BWORKFLOW%5D).
## Naming conventions
### Development
* Gitea: main
* Forgejo: forgejo
* Integration: forgejo-integration
* Feature branches: forgejo-feature-name
### Stable
* Gitea: release/vX.Y
* Forgejo: vX.Y/forgejo
* Integration: vX.Y/forgejo-integration
* Feature branches: vX.Y/forgejo-feature-name
## Rebasing
### *Feature branch*
The *Gitea* branches are mirrored with the Gitea development and stable branches.
On a regular basis, each *Feature branch* is rebased against the base *Gitea* branch.
### *Integration* and *Forgejo*
The latest *Gitea* branch resets the *Integration* branch and all *Feature branches* are merged into it.
If tests pass, the *Forgejo* branch is reset to the tip of the *Integration* branch.
If tests do not pass, an issue is filed to the *Feature branch* that fails the test. Once the issue is resolved, another round of rebasing starts.
## Releasing
When a tag is set to a *Stable* *Forgejo* branch, the CI pipeline creates and uploads binaries and packages.
## Feature branches
All *Feature branches* are based on the {vX.Y/,}forgejo-development branch which provides and other development tools and documenation.
The \*forgejo-development branch is based on the {vX.Y/,}forgejo-ci branch which provides the Woodpecker CI configuration.
The purpose of each *Feature branch* is documented below:
### General purpose
* [forgejo-ci](https://codeberg.org/forgejo/forgejo/src/branch/forgejo-ci) based on [main](https://codeberg.org/forgejo/forgejo/src/branch/main)
Woodpecker CI configuration, including the release process.
* Backports: [v1.18/forgejo-ci](https://codeberg.org/forgejo/forgejo/src/branch/v1.18/forgejo-ci)
* [forgejo-development](https://codeberg.org/forgejo/forgejo/src/branch/forgejo-development) based on [forgejo-ci](https://codeberg.org/forgejo/forgejo/src/branch/forgejo-ci)
Forgejo development tools and documentation.
* Backports: [v1.18/forgejo-development](https://codeberg.org/forgejo/forgejo/src/branch/v1.18/forgejo-development)
### [Federation](https://codeberg.org/forgejo/forgejo/issues?labels=79349)
* [forgejo-federation](https://codeberg.org/forgejo/forgejo/src/branch/forgejo-federation) based on [forgejo-development](https://codeberg.org/forgejo/forgejo/src/branch/forgejo-development)
Federation support for Forgejo
## Pull requests and feature branches
Most people who are used to contributing will be familiar with the workflow of sending a pull request against the default branch. When that happens the reviewer should change the base branch to the appropriate *Feature branch* instead. If the pull request does not fit in any *Feature branch*, the reviewer needs to make decision to either:
* Decline the pull request because it is best contributed to Gitea
* Create a new *Feature branch*
Returning contributors can figure out which *Feature branch* to base their pull request on using the list of *Feature branches*.
## Granularity
*Feature branches* can contain a number of commits grouped together, for instance for branding the documentation, the landing page and the footer. It makes it convenient for people working on that topic to get the big picture without browsing multiple branches. Creating a new *Feature branch* for each individual commit, while possible, is likely to be difficult to work with.
Observing the granularity of the existing *Feature branches* is the best way to figure out what works and what does not. It requires adjustments from time to time depending on the number of contributors and the complexity of the Forgejo codebase that sits on top of Gitea.

View File

@ -1,5 +1,5 @@
#Build stage #Build stage
FROM golang:1.19-alpine3.16 AS build-env FROM codeberg.org/forgejo/golang:1.19-alpine3.16 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY ${GOPROXY:-direct} ENV GOPROXY ${GOPROXY:-direct}
@ -23,8 +23,8 @@ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
# Begin env-to-ini build # Begin env-to-ini build
RUN go build contrib/environment-to-ini/environment-to-ini.go RUN go build contrib/environment-to-ini/environment-to-ini.go
FROM alpine:3.16 FROM codeberg.org/forgejo/alpine:3.16.3
LABEL maintainer="maintainers@gitea.io" LABEL maintainer="contact@forgejo.org"
EXPOSE 22 3000 EXPOSE 22 3000
@ -64,5 +64,9 @@ CMD ["/bin/s6-svscan", "/etc/s6"]
COPY docker/root / COPY docker/root /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
RUN chmod 755 /usr/bin/entrypoint /app/gitea/gitea /usr/local/bin/gitea /usr/local/bin/environment-to-ini #
RUN chmod 755 /etc/s6/gitea/* /etc/s6/openssh/* /etc/s6/.s6-svscan/* # s/755/775/ in the following is to avoid the corrupted layer 4f4fb700ef54
# https://codeberg.org/Codeberg/Community/issues/800#issue-210309
#
RUN chmod 775 /usr/bin/entrypoint /app/gitea/gitea /usr/local/bin/gitea /usr/local/bin/environment-to-ini
RUN chmod 775 /etc/s6/gitea/* /etc/s6/openssh/* /etc/s6/.s6-svscan/*

4
Dockerfile.tmp Normal file
View File

@ -0,0 +1,4 @@
FROM golang:1.19-alpine3.16
RUN pwd

View File

@ -83,7 +83,7 @@ ifneq ($(DRONE_TAG),)
GITEA_VERSION ?= $(VERSION) GITEA_VERSION ?= $(VERSION)
else else
ifneq ($(DRONE_BRANCH),) ifneq ($(DRONE_BRANCH),)
VERSION ?= $(subst release/v,,$(DRONE_BRANCH)) VERSION ?= $(shell echo $(DRONE_BRANCH) | sed -e 's|v\([0-9.][0-9.]*\)/.*|\1|')
else else
VERSION ?= main VERSION ?= main
endif endif
@ -751,7 +751,7 @@ $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@ CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
.PHONY: release .PHONY: release
release: frontend generate release-windows release-linux release-darwin release-copy release-compress vendor release-sources release-docs release-check release: frontend generate release-linux release-copy release-compress vendor release-sources release-check
$(DIST_DIRS): $(DIST_DIRS):
mkdir -p $(DIST_DIRS) mkdir -p $(DIST_DIRS)
@ -768,7 +768,7 @@ endif
.PHONY: release-linux .PHONY: release-linux
release-linux: | $(DIST_DIRS) release-linux: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out forgejo-$(VERSION) .
ifeq ($(CI),true) ifeq ($(CI),true)
cp /build/* $(DIST)/binaries cp /build/* $(DIST)/binaries
endif endif
@ -798,8 +798,8 @@ release-sources: | $(DIST_DIRS)
# bsdtar needs a ^ to prevent matching subdirectories # bsdtar needs a ^ to prevent matching subdirectories
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./) $(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./)
# use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s # use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s
$(eval TRANSFORM := $(shell tar --help | grep -q bsdtar && echo "-s '/^./gitea-src-$(VERSION)/'" || echo "--transform 's|^./|gitea-src-$(VERSION)/|'")) $(eval TRANSFORM := $(shell tar --help | grep -q bsdtar && echo "-s '/^./forgejo-src-$(VERSION)/'" || echo "--transform 's|^./|forgejo-src-$(VERSION)/|'"))
tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz . tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/forgejo-src-$(VERSION).tar.gz .
rm -f $(STORED_VERSION_FILE) rm -f $(STORED_VERSION_FILE)
.PHONY: release-docs .PHONY: release-docs

202
README.md
View File

@ -1,180 +1,46 @@
<p align="center"> # Welcome to Forgejo
<a href="https://gitea.io/">
<img alt="Gitea" src="https://raw.githubusercontent.com/go-gitea/gitea/main/public/img/gitea.svg" width="220"/>
</a>
</p>
<h1 align="center">Gitea - Git with a cup of tea</h1>
<p align="center"> Hi there! Tired of big platforms playing monopoly?
<a href="https://drone.gitea.io/go-gitea/gitea" title="Build Status"> Providing Git hosting for your project, friends, company or community?
<img src="https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg?ref=refs/heads/main"> **Forgejo** (inspired by forĝejo \ˈfor.d͡ʒe.jo\ the Esperanto word for *forge*) has you covered with its intuitive interface,
</a> light and easy hosting and a lot of builtin functionality.
<a href="https://discord.gg/Gitea" title="Join the Discord chat at https://discord.gg/Gitea">
<img src="https://img.shields.io/discord/322538954119184384.svg">
</a>
<a href="https://codecov.io/gh/go-gitea/gitea" title="Codecov">
<img src="https://codecov.io/gh/go-gitea/gitea/branch/main/graph/badge.svg">
</a>
<a href="https://goreportcard.com/report/code.gitea.io/gitea" title="Go Report Card">
<img src="https://goreportcard.com/badge/code.gitea.io/gitea">
</a>
<a href="https://godoc.org/code.gitea.io/gitea" title="GoDoc">
<img src="https://godoc.org/code.gitea.io/gitea?status.svg">
</a>
<a href="https://github.com/go-gitea/gitea/releases/latest" title="GitHub release">
<img src="https://img.shields.io/github/release/go-gitea/gitea.svg">
</a>
<a href="https://www.codetriage.com/go-gitea/gitea" title="Help Contribute to Open Source">
<img src="https://www.codetriage.com/go-gitea/gitea/badges/users.svg">
</a>
<a href="https://opencollective.com/gitea" title="Become a backer/sponsor of gitea">
<img src="https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen">
</a>
<a href="https://opensource.org/licenses/MIT" title="License: MIT">
<img src="https://img.shields.io/badge/License-MIT-blue.svg">
</a>
<a href="https://gitpod.io/#https://github.com/go-gitea/gitea">
<img
src="https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod"
alt="Contribute with Gitpod"
/>
</a>
<a href="https://crowdin.com/project/gitea" title="Crowdin">
<img src="https://badges.crowdin.net/gitea/localized.svg">
</a>
<a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs">
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main">
</a>
<a href="https://www.bountysource.com/teams/gitea" title="Bountysource">
<img src="https://img.shields.io/bountysource/team/gitea/activity">
</a>
</p>
<p align="center"> Forgejo was forked from the well-known [Gitea](https://gitea.io) project in 2022,
<a href="README_ZH.md">View this document in Chinese</a> because we think that the project should be owned by an independent community instead of a for-profit company.
</p> If you second that, then Forgejo is for you!
Our promise: **Independent Free/Libre Software forever!**
## Purpose
The goal of this project is to make the easiest, fastest, and most ## What does Forgejo offer?
painless way of setting up a self-hosted Git service.
As Gitea is written in Go, it works across **all** the platforms and If you want to know what Forgejo is like,
architectures that are supported by Go, including Linux, macOS, and you can check out public instances<!-- could become link to list -->,
Windows on x86, amd64, ARM and PowerPC architectures. e.g. [Codeberg.org](https://codeberg.org).
You can try it out using [the online demo](https://try.gitea.io/).
This project has been
[forked](https://blog.gitea.io/2016/12/welcome-to-gitea/) from
[Gogs](https://gogs.io) since November of 2016, but a lot has changed.
## Building If you like any of the following, Forgejo is literally meant for you:
From the root of the source tree, run: - Lightweight: Forgejo can easily be hosted on nearly **every machine**.
Running on a Raspberry? Small cloud instance? No problem!
- Intuitive: We aim for a user experience that is **friendly** to newcomers and **productive** for power users.
- Project management: Besides Git hosting, Forgejo offers issues,
pull requests, wikis, kanban boards and much more to **coordinate with your team**.
- Publishing: Have something to share? Use **releases** to host your software for download,
or use the **package registry** to publish it for docker, npm and many other package managers.
- Customizable: Want to change your look? Change some settings?
There are many **config switches** to make Forgejo work exactly like you want.
- Powerful: Organizations & team permissions, CI integration, Code Search, LDAP, OAuth and much more.
If you have **advanced needs**, Forgejo has you covered.
- Privacy: From update checker to default settings: Forgejo is built to be **privacy first** for you and your crew.
- Federation: (WIP) We are actively working to connect software forges with each other through **ActivityPub**,
and create a collaborative network of personal instances.
Interested? [Read more below](#Status-of-federation)
TAGS="bindata" make build ## Learn more
or if SQLite support is required: We're still working on our website.
In the meantime, you can <a href="https://floss.social/@Forgejo" rel="me">find us on the Fediverse</a> or hop into [our Matrix room](https://matrix.to/#/#Forgejo-chat:matrix.org) if you have any questions or want to get involved.
TAGS="bindata sqlite sqlite_unlock_notify" make build
The `build` target is split into two sub-targets: ## Get involved
- `make backend` which requires [Go Stable](https://go.dev/dl/), required version is defined in [go.mod](/go.mod). If you are interested in making Forgejo better, either by reporting a bug or by changing the governance, please [take a look at the contribution guide](CONTRIBUTING.md).
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and Internet connectivity to download npm dependencies.
When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js and Internet connectivity.
Parallelism (`make -j <num>`) is not supported.
More info: https://docs.gitea.io/en-us/install-from-source/
## Using
./gitea web
NOTE: If you're interested in using our APIs, we have experimental
support with [documentation](https://try.gitea.io/api/swagger).
## Contributing
Expected workflow is: Fork -> Patch -> Push -> Pull Request
NOTES:
1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.**
2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
## Translating
Translations are done through Crowdin. If you want to translate to a new language ask one of the managers in the Crowdin project to add a new language there.
You can also just create an issue for adding a language or ask on discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty but we hope to fill it as questions pop up.
https://docs.gitea.io/en-us/translation-guidelines/
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea)
## Further information
For more information and instructions about how to install Gitea, please look at our [documentation](https://docs.gitea.io/en-us/).
If you have questions that are not covered by the documentation, you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://discourse.gitea.io/).
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea).
The Hugo-based documentation theme is hosted at [gitea/theme](https://gitea.com/gitea/theme).
The official Gitea CLI is developed at [gitea/tea](https://gitea.com/gitea/tea).
## Authors
- [Maintainers](https://github.com/orgs/go-gitea/people)
- [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
- [Translators](options/locale/TRANSLATORS)
## Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/gitea#backer)]
<a href="https://opencollective.com/gitea#backers" target="_blank"><img src="https://opencollective.com/gitea/backers.svg?width=890"></a>
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/gitea#sponsor)]
<a href="https://opencollective.com/gitea/sponsor/0/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/1/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/2/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/3/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/4/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/5/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/6/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
<a href="https://cynkra.com/" target="_blank"><img src="https://images.opencollective.com/cynkra/logo/square/64/192.png"></a>
## FAQ
**How do you pronounce Gitea?**
Gitea is pronounced [/ɡɪti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea" with a hard g.
**Why is this not hosted on a Gitea instance?**
We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
## License
This project is licensed under the MIT License.
See the [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) file
for the full license text.
## Screenshots
Looking for an overview of the interface? Check it out!
|![Dashboard](https://dl.gitea.io/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.io/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.io/screenshots/global_issues.png)|
|:---:|:---:|:---:|
|![Branches](https://dl.gitea.io/screenshots/branches.png)|![Web Editor](https://dl.gitea.io/screenshots/web_editor.png)|![Activity](https://dl.gitea.io/screenshots/activity.png)|
|![New Migration](https://dl.gitea.io/screenshots/migration.png)|![Migrating](https://dl.gitea.io/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)
![Pull Request Dark](https://dl.gitea.io/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.io/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.io/screenshots/diff_dark.png)|

View File

@ -1,98 +0,0 @@
<p align="center">
<a href="https://gitea.io/">
<img alt="Gitea" src="https://raw.githubusercontent.com/go-gitea/gitea/main/public/img/gitea.svg" width="220"/>
</a>
</p>
<h1 align="center">Gitea - Git with a cup of tea</h1>
<p align="center">
<a href="https://drone.gitea.io/go-gitea/gitea" title="Build Status">
<img src="https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg?ref=refs/heads/main">
</a>
<a href="https://discord.gg/Gitea" title="Join the Discord chat at https://discord.gg/Gitea">
<img src="https://img.shields.io/discord/322538954119184384.svg">
</a>
<a href="https://codecov.io/gh/go-gitea/gitea" title="Codecov">
<img src="https://codecov.io/gh/go-gitea/gitea/branch/main/graph/badge.svg">
</a>
<a href="https://goreportcard.com/report/code.gitea.io/gitea" title="Go Report Card">
<img src="https://goreportcard.com/badge/code.gitea.io/gitea">
</a>
<a href="https://godoc.org/code.gitea.io/gitea" title="GoDoc">
<img src="https://godoc.org/code.gitea.io/gitea?status.svg">
</a>
<a href="https://github.com/go-gitea/gitea/releases/latest" title="GitHub release">
<img src="https://img.shields.io/github/release/go-gitea/gitea.svg">
</a>
<a href="https://www.codetriage.com/go-gitea/gitea" title="Help Contribute to Open Source">
<img src="https://www.codetriage.com/go-gitea/gitea/badges/users.svg">
</a>
<a href="https://opencollective.com/gitea" title="Become a backer/sponsor of gitea">
<img src="https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen">
</a>
<a href="https://opensource.org/licenses/MIT" title="License: MIT">
<img src="https://img.shields.io/badge/License-MIT-blue.svg">
</a>
<a href="https://gitpod.io/#https://github.com/go-gitea/gitea">
<img
src="https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod"
alt="Contribute with Gitpod"
/>
</a>
<a href="https://crowdin.com/project/gitea" title="Crowdin">
<img src="https://badges.crowdin.net/gitea/localized.svg">
</a>
<a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs">
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main">
</a>
<a href="https://img.shields.io/bountysource/team/gitea" title="Bountysource">
<img src="https://img.shields.io/bountysource/team/gitea/activity">
</a>
</p>
<p align="center">
<a href="README.md">View this document in English</a>
</p>
## 目标
Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86amd64还包括 ARM 和 PowerPC。
如果您想试用一下,请访问 [在线Demo](https://try.gitea.io/)
## 提示
1. **开始贡献代码之前请确保你已经看过了 [贡献者向导(英文)](CONTRIBUTING.md)**.
2. 所有的安全问题,请私下发送邮件给 **security@gitea.io**。谢谢!
3. 如果你要使用API请参见 [API 文档](https://godoc.org/code.gitea.io/sdk/gitea).
## 文档
关于如何安装请访问我们的 [文档站](https://docs.gitea.io/zh-cn/),如果没有找到对应的文档,你也可以通过 [Discord - 英文](https://discord.gg/gitea) 和 QQ群 328432459 来和我们交流。
## 贡献流程
Fork -> Patch -> Push -> Pull Request
## 翻译
多语言翻译是基于Crowdin进行的.
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea)
## 作者
* [Maintainers](https://github.com/orgs/go-gitea/people)
* [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
* [Translators](options/locale/TRANSLATORS)
## 授权许可
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件中。
## 截图
|![Dashboard](https://dl.gitea.io/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.io/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.io/screenshots/global_issues.png)|
|:---:|:---:|:---:|
|![Branches](https://dl.gitea.io/screenshots/branches.png)|![Web Editor](https://dl.gitea.io/screenshots/web_editor.png)|![Activity](https://dl.gitea.io/screenshots/activity.png)|
|![New Migration](https://dl.gitea.io/screenshots/migration.png)|![Migrating](https://dl.gitea.io/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)
![Pull Request Dark](https://dl.gitea.io/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.io/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.io/screenshots/diff_dark.png)|

View File

@ -1,83 +0,0 @@
# Reporting security issues
The Gitea maintainers take security seriously.
If you discover a security issue, please bring it to their attention right away!
## Reporting a Vulnerability
Please **DO NOT** file a public issue, instead send your report privately to `security@gitea.io`.
## Protecting Security Information
Due to the sensitive nature of security information, you can use below GPG public key encrypt your mail body.
The PGP key is valid until June 24, 2024.
```
Key ID: 6FCD2D5B
Key Type: RSA
Expires: 6/24/2024
Key Size: 4096/4096
Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B
```
UserID: Gitea Security <security@gitea.io>
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGK1Z/4BEADFMqXA9DeeChmSxUjF0Be5sq99ZUhgrZjcN/wOzz0wuCJZC0l8
4uC+d6mfv7JpJYlzYzOK97/x5UguKHkYNZ6mm1G9KHaXmoIBDLKDzfPdJopVNv2r
OajijaE0uMCnMjadlg5pbhMLRQG8a9J32yyaz7ZEAw72Ab31fvvcA53NkuqO4j2w
k7dtFQzhbNOYV0VffQT90WDZdalYHB1JHyEQ+70U9OjVD5ggNYSzX98Eu3Hjn7V7
kqFrcAxr5TE1elf0IXJcuBJtFzQSTUGlQldKOHtGTGgGjj9r/FFAE5ioBgVD05bV
rEEgIMM/GqYaG/nbNpWE6P3mEc2Mnn3pZaRJL0LuF26TLjnqEcMMDp5iIhLdFzXR
3tMdtKgQFu+Mtzs3ipwWARYgHyU09RJsI2HeBx7RmZO/Xqrec763Z7zdJ7SpCn0Z
q+pHZl24JYR0Kf3T/ZiOC0cGd2QJqpJtg5J6S/OqfX9NH6MsCczO8pUC1N/aHH2X
CTme2nF56izORqDWKoiICteL3GpYsCV9nyCidcCmoQsS+DKvE86YhIhVIVWGRY2F
lzpAjnN9/KLtQroutrm+Ft0mdjDiJUeFVl1cOHDhoyfCsQh62HumoyZoZvqzQd6e
AbN11nq6aViMe2Q3je1AbiBnRnQSHxt1Tc8X4IshO3MQK1Sk7oPI6LA5oQARAQAB
tCJHaXRlYSBTZWN1cml0eSA8c2VjdXJpdHlAZ2l0ZWEuaW8+iQJXBBMBCABBFiEE
PeA9HhRKfwaTWZncqv0jgW/NLVsFAmK1Z/4CGwMFCQPCZwAFCwkIBwICIgIGFQoJ
CAsCBBYCAwECHgcCF4AACgkQqv0jgW/NLVvnyxAAhxyNnWzw/rQO2qhzqicmZM94
njSbOg+U2qMBvCdaqCQQeC+uaMmMzkDPanUUmLcyCkWqfCjPNjeSXAkE9npepVJI
4HtmgxZQ94OU/h3CLbft+9GVRzUkVI29TSYGdvNtV2/BkNGoFFnKWQr119um0o6A
bgha2Uy5uY8o3ZIoiKkiHRaEoWIjjeBxJxYAojsZY4YElUmsQ3ik2joG6rhFesTa
ofVt/bL8G2xzpOG26WGIxBbqf2qjV6OtZ0hu/vtTPHeIWMLq0Mz0V3PEDQWfkGPE
i2RYxxYDs2xzJhSQWqTNVLSq0m5xTJnbHhQPfdCX4C2jvFKgLdfmytQq49S7jiJb
Z03HVOZ/PsyBlQfH9xJi06R5yQCMEA8h8Z5r3/NXW09kQ6OFRe6xshoTcxZGRPTo
srhwr3uPbmCRh+YEl7qBLU6+BC5k8IRTZXqhrj/aPJu3MxgbgwV8u3vLoFSXM2lb
a61FgeCQ0O7lkgVswwF0RppCaH9Ul3ZDapet/vCRg4NVwm9zOI/8q/Vj0FKA1GDR
JhRu8+Ce8zlFL65D34t+PprAzSeTlbv9um3x/ZIjCco7EEKSBylt+AZj/VyA6+e5
kjOQwRRc6dFJWBcorsSI2dG+H+QMF7ZabzmeCcz1v9HjLOPzYHoZAHhCmSppWTvX
AJy6+lhfW2OUTqQeYSi5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen
T+p07yCSSoSlmnJHCQmwh4vfg1blyz0zZ4vkIhtpHsEgc+ZAG+WQXSsJ2iRz+eSN
GwoOQl4XC3n+QWkc1ws+btr48+6UqXIQU+F8TPQyx/PIgi2nZXJB7f5+mjCqsk46
XvH4nTr4kJjuqMSR/++wvre2qNQRa/q/dTsK0OaN/mJsdX6Oi+aGNaQJUhIG7F+E
ZDMkn/O6xnwWNzy/+bpg43qH/Gk0eakOmz5NmQLRkV58SZLiJvuCUtkttf6CyhnX
03OcWaajv5W8qA39dBYQgDrrPbBWUnwfO3yMveqhwV4JjDoe8sPAyn1NwzakNYqP
RzsWyLrLS7R7J9s3FkZXhQw/QQcsaSMcGNQO047dm1P83N8JY5aEpiRo9zSWjoiw
qoExANj5lUTZPe8M50lI182FrcjAN7dClO3QI6pg7wy0erMxfFly3j8UQ91ysS9T
s+GsP9I3cmWWQcKYxWHtE8xTXnNCVPFZQj2nwhJzae8ypfOtulBRA3dUKWGKuDH/
axFENhUsT397aOU3qkP/od4a64JyNIEo4CTTSPVeWd7njsGqli2U3A4xL2CcyYvt
D/MWcMBGEoLSNTswwKdom4FaJpn5KThnK/T0bQcmJblJhoCtppXisbexZnCpuS0x
Zdlm2T14KJ3LABEBAAGJAjwEGAEIACYWIQQ94D0eFEp/BpNZmdyq/SOBb80tWwUC
YrVn/gIbDAUJA8JnAAAKCRCq/SOBb80tWyTBD/9AGpW6QoDF7zYjHAozH9S5RGCA
Y7E82dG/0xmFUwPprAG0BKmmgU6TiipyVGmKIXGYYYU92pMnbvXkYQMoa+WJNncN
D3fY52UeXeffTf4cFpStlzi9xgYtOLhFamzYu/4xhkjOX+xhOSXscCiFRyT8cF3B
O6c5BHU+Zj0/rGPgOyPUbx7l7B9MubB/41nNX35k08e+8T3wtWDb4XF+15HnRfva
6fblO8wgU25Orv2Rm1jnKGa9DxJ8nE40IMrqDapENtDuL+zKJsvR0+ptWvEyL56U
GtJJG5un6mXiLKuRQT0DEv4MdZRHDgDstDnqcbEiazVEbUuvhZZob6lRY2A19m1+
7zfnDxkhqCA1RCnv4fdvcPdCMMFHwLpdhjgW0aI/uwgwrvsEz5+JRlnLvdQHlPAg
q7l2fGcBSpz9U0ayyfRPjPntsNCtZl1UDxGLeciPkZhyG84zEWQbk/j52ZpRN+Ik
ALpRLa8RBFmFSmXDUmwQrmm1EmARyQXwweKU31hf8ZGbCp2lPuRYm1LuGiirXSVP
GysjRAJgW+VRpBKOzFQoUAUbReVWSaCwT8s17THzf71DdDb6CTj31jMLLYWwBpA/
i73DgobDZMIGEZZC1EKqza8eh11xfyHFzGec03tbh+lIen+5IiRtWiEWkDS9ll0G
zgS/ZdziCvdAutqnGA==
=gZWO
-----END PGP PUBLIC KEY BLOCK-----
```
Security reports are greatly appreciated and we will publicly thank you for it, although we keep your name confidential if you request it.

File diff suppressed because one or more lines are too long

108
cmd/f3.go Normal file
View File

@ -0,0 +1,108 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"context"
"fmt"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/services/f3/util"
"github.com/urfave/cli"
"lab.forgefriends.org/friendlyforgeformat/gof3"
f3_format "lab.forgefriends.org/friendlyforgeformat/gof3/format"
)
var CmdF3 = cli.Command{
Name: "f3",
Usage: "Friendly Forge Format (F3) format export/import.",
Description: "Import or export a repository from or to the Friendly Forge Format (F3) format.",
Action: runF3,
Flags: []cli.Flag{
cli.StringFlag{
Name: "directory",
Value: "./f3",
Usage: "Path of the directory where the F3 dump is stored",
},
cli.StringFlag{
Name: "user",
Value: "",
Usage: "The name of the user who owns the repository",
},
cli.StringFlag{
Name: "repository",
Value: "",
Usage: "The name of the repository",
},
cli.BoolFlag{
Name: "no-pull-request",
Usage: "Do not dump pull requests",
},
cli.BoolFlag{
Name: "import",
Usage: "Import from the directory",
},
cli.BoolFlag{
Name: "export",
Usage: "Export to the directory",
},
},
}
func runF3(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
defer cancel()
if err := initDB(stdCtx); err != nil {
return err
}
if err := git.InitSimple(stdCtx); err != nil {
return err
}
return RunF3(stdCtx, ctx)
}
func RunF3(stdCtx context.Context, ctx *cli.Context) error {
doer, err := user_model.GetAdminUser()
if err != nil {
return err
}
features := gof3.AllFeatures
if ctx.Bool("no-pull-request") {
features.PullRequests = false
}
gitea := util.GiteaForgeRoot(stdCtx, features, doer)
f3 := util.F3ForgeRoot(stdCtx, features, ctx.String("directory"))
if ctx.Bool("export") {
gitea.Forge.Users.List()
user := gitea.Forge.Users.GetFromFormat(&f3_format.User{UserName: ctx.String("user")})
if user.IsNil() {
return fmt.Errorf("%s is not a known user", ctx.String("user"))
}
user.Projects.List()
project := user.Projects.GetFromFormat(&f3_format.Project{Name: ctx.String("repository")})
if project.IsNil() {
return fmt.Errorf("%s/%s is not a known repository", ctx.String("user"), ctx.String("repository"))
}
f3.Forge.Mirror(gitea.Forge, user, project)
fmt.Println("exported")
} else if ctx.Bool("import") {
gitea.Forge.Mirror(f3.Forge)
fmt.Println("imported")
} else {
return fmt.Errorf("either --import or --export must be specified")
}
return nil
}

View File

@ -2337,6 +2337,15 @@ ROUTER = console
;; If a domain is allowed by ALLOWED_DOMAINS, this option will be ignored. ;; If a domain is allowed by ALLOWED_DOMAINS, this option will be ignored.
;ALLOW_LOCALNETWORKS = false ;ALLOW_LOCALNETWORKS = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[F3]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Enable/Disable Friendly Forge Format (F3)
;ENABLED = true
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[federation] ;[federation]

6
go.mod
View File

@ -4,7 +4,7 @@ go 1.18
require ( require (
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b
code.gitea.io/sdk/gitea v0.15.1 code.gitea.io/sdk/gitea v0.15.1-0.20220915214501-aef4e5e2bd47
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681 gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681
gitea.com/go-chi/cache v0.2.0 gitea.com/go-chi/cache v0.2.0
@ -103,6 +103,7 @@ require (
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
lab.forgefriends.org/friendlyforgeformat/gof3 v0.0.0-20221129111956-573e4920cf5b
mvdan.cc/xurls/v2 v2.4.0 mvdan.cc/xurls/v2 v2.4.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
xorm.io/builder v0.3.11 xorm.io/builder v0.3.11
@ -158,6 +159,7 @@ require (
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
@ -193,6 +195,7 @@ require (
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.1 // indirect github.com/google/btree v1.0.1 // indirect
github.com/google/certificate-transparency-go v1.1.2-0.20210511102531-373a877eec92 // indirect github.com/google/certificate-transparency-go v1.1.2-0.20210511102531-373a877eec92 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect
@ -283,6 +286,7 @@ require (
go.uber.org/atomic v1.10.0 // indirect go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect go.uber.org/zap v1.23.0 // indirect
golang.org/x/exp v0.0.0-20220516143420-24438e51023a // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect

14
go.sum
View File

@ -64,12 +64,11 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b h1:uv9a8eGSdQ8Dr4HyUcuHFfDsk/QuwO+wf+Y99RYdxY0= code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b h1:uv9a8eGSdQ8Dr4HyUcuHFfDsk/QuwO+wf+Y99RYdxY0=
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M= code.gitea.io/sdk/gitea v0.15.1-0.20220915214501-aef4e5e2bd47 h1:e9J9QdBk4NbdskZyGmJcaUGC/agG0UHMGyMrjgPb+6Q=
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA= code.gitea.io/sdk/gitea v0.15.1-0.20220915214501-aef4e5e2bd47/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY= codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM= codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
@ -377,6 +376,8 @@ github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
@ -489,6 +490,7 @@ github.com/go-enry/go-enry/v2 v2.8.3 h1:BwvNrN58JqBJhyyVdZSl5QD3xoxEEGYUrRyPh31F
github.com/go-enry/go-enry/v2 v2.8.3/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ= github.com/go-enry/go-enry/v2 v2.8.3/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8= github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8=
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
@ -1600,12 +1602,14 @@ golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891 h1:WhEPFM1Ck5gaKybeSWvzI7Y/cd8K9K5tJGRxXMACOBA= golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891 h1:WhEPFM1Ck5gaKybeSWvzI7Y/cd8K9K5tJGRxXMACOBA=
@ -1621,6 +1625,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/exp v0.0.0-20220516143420-24438e51023a h1:tiLLxEjKNE6Hrah/Dp/cyHvsyjDLcMFSocOHO5XDmOM=
golang.org/x/exp v0.0.0-20220516143420-24438e51023a/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -2255,6 +2261,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
lab.forgefriends.org/friendlyforgeformat/gof3 v0.0.0-20221129111956-573e4920cf5b h1:0RwrwwyefLzAYPsmCxQqURlChCg6tW0y17g1PhPrm2Y=
lab.forgefriends.org/friendlyforgeformat/gof3 v0.0.0-20221129111956-573e4920cf5b/go.mod h1:v2t/aa0w224NzBcx1mdzuxJSRWPcaaanQRhV43v7+yw=
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=

View File

@ -75,6 +75,7 @@ arguments - which can alternatively be run by running the subcommand web.`
cmd.CmdDocs, cmd.CmdDocs,
cmd.CmdDumpRepository, cmd.CmdDumpRepository,
cmd.CmdRestoreRepository, cmd.CmdRestoreRepository,
cmd.CmdF3,
} }
// Now adjust these commands to add our global configuration options // Now adjust these commands to add our global configuration options

View File

@ -229,6 +229,21 @@ func GetRepoTopicByName(ctx context.Context, repoID int64, topicName string) (*T
return nil, err return nil, err
} }
// GetRepoTopicByID retrieves topic from ID for a repo if it exist
func GetRepoTopicByID(ctx context.Context, repoID, topicID int64) (*Topic, error) {
cond := builder.NewCond()
var topic Topic
cond = cond.And(builder.Eq{"repo_topic.repo_id": repoID}).And(builder.Eq{"topic.id": topicID})
sess := db.GetEngine(ctx).Table("topic").Where(cond)
sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id")
if has, err := sess.Select("topic.*").Get(&topic); err != nil {
return nil, err
} else if !has {
return nil, ErrTopicNotExist{""}
}
return &topic, nil
}
// AddTopic adds a topic name to a repository (if it does not already have it) // AddTopic adds a topic name to a repository (if it does not already have it)
func AddTopic(repoID int64, topicName string) (*Topic, error) { func AddTopic(repoID int64, topicName string) (*Topic, error) {
ctx, committer, err := db.TxContext(db.DefaultContext) ctx, committer, err := db.TxContext(db.DefaultContext)

View File

@ -102,7 +102,7 @@ func ToPullReviewCommentList(ctx context.Context, review *issues_model.Review, d
Path: comment.TreePath, Path: comment.TreePath,
CommitID: comment.CommitSHA, CommitID: comment.CommitSHA,
OrigCommitID: comment.OldRef, OrigCommitID: comment.OldRef,
DiffHunk: patch2diff(comment.Patch), DiffHunk: Patch2diff(comment.Patch),
HTMLURL: comment.HTMLURL(), HTMLURL: comment.HTMLURL(),
HTMLPullURL: review.Issue.HTMLURL(), HTMLPullURL: review.Issue.HTMLURL(),
} }
@ -119,7 +119,7 @@ func ToPullReviewCommentList(ctx context.Context, review *issues_model.Review, d
return apiComments, nil return apiComments, nil
} }
func patch2diff(patch string) string { func Patch2diff(patch string) string {
split := strings.Split(patch, "\n@@") split := strings.Split(patch, "\n@@")
if len(split) == 2 { if len(split) == 2 {
return "@@" + split[1] return "@@" + split[1]

View File

@ -1,54 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package stats
import (
"context"
"path/filepath"
"testing"
"time"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
_ "code.gitea.io/gitea/models"
"github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
)
func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
GiteaRootPath: filepath.Join("..", "..", ".."),
})
}
func TestRepoStatsIndex(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
setting.Cfg = ini.Empty()
setting.NewQueueService()
err := Init()
assert.NoError(t, err)
repo, err := repo_model.GetRepositoryByID(1)
assert.NoError(t, err)
err = UpdateRepoIndexer(repo)
assert.NoError(t, err)
queue.GetManager().FlushAll(context.Background(), 5*time.Second)
status, err := repo_model.GetIndexerStatus(db.DefaultContext, repo, repo_model.RepoIndexerTypeStats)
assert.NoError(t, err)
assert.Equal(t, "65f1bf27bc3bf70f64657658635e66094edbcb4d", status.CommitSha)
langs, err := repo_model.GetTopLanguageStats(repo, 5)
assert.NoError(t, err)
assert.Empty(t, langs)
}

24
modules/setting/f3.go Normal file
View File

@ -0,0 +1,24 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package setting
import (
"code.gitea.io/gitea/modules/log"
)
// Friendly Forge Format (F3) settings
var (
F3 = struct {
Enabled bool
}{
Enabled: true,
}
)
func newF3Service() {
if err := Cfg.Section("F3").MapTo(&F3); err != nil {
log.Fatal("Failed to map F3 settings: %v", err)
}
}

View File

@ -1340,6 +1340,7 @@ func NewServices() {
newProject() newProject()
newMimeTypeMap() newMimeTypeMap()
newFederationService() newFederationService()
newF3Service()
} }
// NewServicesForInstall initializes the services for install // NewServicesForInstall initializes the services for install

View File

@ -0,0 +1,5 @@
This software is copyrighted. Unlimited copying and redistribution
of this package and/or its individual files are permitted
as long as there are no modifications. Modifications, and
redistribution of modifications, are also permitted, but
only if the resulting package and/or files are renamed.

View File

@ -0,0 +1,15 @@
Copyright (c) 1995 David Nugent <davidn@blaze.net.au>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, is permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice immediately at the beginning of the file, without modification, this list of conditions, and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. This work was done expressly for inclusion into FreeBSD. Other use is permitted provided this notation is included.
4. Absolutely no warranty of function or purpose is made by the author David Nugent.
5. Modifications may be freely made to this file providing the above conditions are met.

163
services/f3/driver/asset.go Normal file
View File

@ -0,0 +1,163 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"fmt"
"io"
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/services/attachment"
"github.com/google/uuid"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type Asset struct {
repo_model.Attachment
DownloadFunc func() io.ReadCloser
}
func AssetConverter(f *repo_model.Attachment) *Asset {
return &Asset{
Attachment: *f,
}
}
func (o Asset) GetID() int64 {
return o.ID
}
func (o *Asset) SetID(id int64) {
o.ID = id
}
func (o *Asset) IsNil() bool {
return o.ID == 0
}
func (o *Asset) Equals(other *Asset) bool {
return o.Name == other.Name
}
func (o *Asset) ToFormat() *format.ReleaseAsset {
return &format.ReleaseAsset{
Common: format.Common{Index: o.ID},
Name: o.Name,
Size: int(o.Size),
DownloadCount: int(o.DownloadCount),
Created: o.CreatedUnix.AsTime(),
DownloadURL: o.DownloadURL(),
DownloadFunc: o.DownloadFunc,
}
}
func (o *Asset) FromFormat(asset *format.ReleaseAsset) {
*o = Asset{
Attachment: repo_model.Attachment{
ID: asset.GetID(),
Name: asset.Name,
Size: int64(asset.Size),
DownloadCount: int64(asset.DownloadCount),
CreatedUnix: timeutil.TimeStamp(asset.Created.Unix()),
},
DownloadFunc: asset.DownloadFunc,
}
}
type AssetProvider struct {
g *Gitea
}
func (o *AssetProvider) GetReferences(asset *Asset) []common.Reference {
return nil
}
func (o *AssetProvider) ToFormat(asset *Asset) *format.ReleaseAsset {
httpClient := o.g.GetNewMigrationHTTPClient()()
a := asset.ToFormat()
a.DownloadFunc = func() io.ReadCloser {
o.g.GetLogger().Debug("download from %s", asset.DownloadURL())
req, err := http.NewRequest("GET", asset.DownloadURL(), nil)
if err != nil {
panic(err)
}
resp, err := httpClient.Do(req)
if err != nil {
panic(fmt.Errorf("while downloading %s %w", asset.DownloadURL(), err))
}
// resp.Body is closed by the consumer
return resp.Body
}
return a
}
func (o *AssetProvider) FromFormat(p *format.ReleaseAsset) *Asset {
var asset Asset
asset.FromFormat(p)
return &asset
}
func (o *AssetProvider) ProcessObject(user *User, project *Project, release *Release, asset *Asset) {
}
func (o *AssetProvider) GetObjects(user *User, project *Project, release *Release, page int) []*Asset {
if page > 1 {
return []*Asset{}
}
r, err := repo_model.GetReleaseByID(o.g.ctx, release.GetID())
if err != nil {
panic(err)
}
if err := r.LoadAttributes(o.g.ctx); err != nil {
panic(fmt.Errorf("error while listing assets: %v", err))
}
return util.ConvertMap[*repo_model.Attachment, *Asset](r.Attachments, AssetConverter)
}
func (o *AssetProvider) Get(user *User, project *Project, release *Release, exemplar *Asset) *Asset {
id := exemplar.GetID()
asset, err := repo_model.GetAttachmentByID(o.g.ctx, id)
if repo_model.IsErrAttachmentNotExist(err) {
return &Asset{}
}
if err != nil {
panic(err)
}
return AssetConverter(asset)
}
func (o *AssetProvider) Put(user *User, project *Project, release *Release, asset *Asset) *Asset {
asset.ID = 0
asset.UploaderID = user.GetID()
asset.RepoID = project.GetID()
asset.ReleaseID = release.GetID()
asset.UUID = uuid.New().String()
download := asset.DownloadFunc()
defer download.Close()
a, err := attachment.NewAttachment(&asset.Attachment, download)
if err != nil {
panic(err)
}
return o.Get(user, project, release, AssetConverter(a))
}
func (o *AssetProvider) Delete(user *User, project *Project, release *Release, asset *Asset) *Asset {
a := o.Get(user, project, release, asset)
if !a.IsNil() {
err := repo_model.DeleteAttachment(&a.Attachment, true)
if err != nil {
panic(err)
}
}
return a
}

View File

@ -0,0 +1,155 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil"
comment_service "code.gitea.io/gitea/services/comments"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type Comment struct {
issues_model.Comment
}
func CommentConverter(f *issues_model.Comment) *Comment {
return &Comment{
Comment: *f,
}
}
func (o Comment) GetID() int64 {
return o.Comment.ID
}
func (o *Comment) SetID(id int64) {
o.Comment.ID = id
}
func (o *Comment) IsNil() bool {
return o.ID == 0
}
func (o *Comment) Equals(other *Comment) bool {
return o.Comment.ID == other.Comment.ID
}
func (o *Comment) ToFormat() *format.Comment {
return &format.Comment{
Common: format.Common{Index: o.Comment.ID},
IssueIndex: o.Comment.IssueID,
PosterID: o.Comment.PosterID,
Content: o.Comment.Content,
Created: o.Comment.CreatedUnix.AsTime(),
Updated: o.Comment.UpdatedUnix.AsTime(),
}
}
func (o *Comment) FromFormat(comment *format.Comment) {
*o = Comment{
Comment: issues_model.Comment{
ID: comment.Index,
PosterID: comment.PosterID,
Poster: &user_model.User{
ID: comment.PosterID,
},
Content: comment.Content,
CreatedUnix: timeutil.TimeStamp(comment.Created.Unix()),
UpdatedUnix: timeutil.TimeStamp(comment.Updated.Unix()),
},
}
}
type CommentProvider struct {
g *Gitea
}
func (o *CommentProvider) GetReferences(comment *Comment) []common.Reference {
return nil
}
func (o *CommentProvider) ToFormat(comment *Comment) *format.Comment {
return comment.ToFormat()
}
func (o *CommentProvider) FromFormat(f *format.Comment) *Comment {
var comment Comment
comment.FromFormat(f)
return &comment
}
func (o *CommentProvider) GetObjects(user *User, project *Project, commentable common.ContainerObjectInterface, page int) []*Comment {
comments, err := issues_model.FindComments(o.g.ctx, &issues_model.FindCommentsOptions{
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
RepoID: project.GetID(),
IssueID: commentable.GetID(),
Type: issues_model.CommentTypeComment,
})
if err != nil {
panic(fmt.Errorf("error while listing comment: %v", err))
}
return util.ConvertMap[*issues_model.Comment, *Comment](comments, CommentConverter)
}
func (o *CommentProvider) ProcessObject(user *User, project *Project, commentable common.ContainerObjectInterface, comment *Comment) {
if err := comment.LoadIssue(o.g.ctx); err != nil {
panic(err)
}
if err := comment.LoadPoster(o.g.ctx); err != nil {
panic(err)
}
}
func (o *CommentProvider) Get(user *User, project *Project, commentable common.ContainerObjectInterface, comment *Comment) *Comment {
id := comment.GetID()
c, err := issues_model.GetCommentByID(o.g.ctx, id)
if issues_model.IsErrCommentNotExist(err) {
return &Comment{}
}
if err != nil {
panic(err)
}
co := CommentConverter(c)
o.ProcessObject(user, project, commentable, co)
return co
}
func (o *CommentProvider) Put(user *User, project *Project, commentable common.ContainerObjectInterface, comment *Comment) *Comment {
var issue *issues_model.Issue
switch c := commentable.(type) {
case *PullRequest:
issue = c.PullRequest.Issue
case *Issue:
issue = &c.Issue
default:
panic(fmt.Errorf("unexpected type %T", commentable))
}
c, err := comment_service.CreateIssueComment(o.g.ctx, o.g.GetDoer(), &project.Repository, issue, comment.Content, nil)
if err != nil {
panic(err)
}
return o.Get(user, project, commentable, CommentConverter(c))
}
func (o *CommentProvider) Delete(user *User, project *Project, commentable common.ContainerObjectInterface, comment *Comment) *Comment {
c := o.Get(user, project, commentable, comment)
if !c.IsNil() {
err := issues_model.DeleteComment(o.g.ctx, &c.Comment)
if err != nil {
panic(err)
}
}
return c
}

View File

@ -0,0 +1,116 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"context"
"fmt"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/migrations"
"lab.forgefriends.org/friendlyforgeformat/gof3"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/driver"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
)
type Options struct {
gof3.Options
Doer *user_model.User
}
type Gitea struct {
perPage int
ctx context.Context
options *Options
}
func (o *Gitea) GetPerPage() int {
return o.perPage
}
func (o *Gitea) GetOptions() gof3.OptionsInterface {
return o.options
}
func (o *Gitea) SetOptions(options gof3.OptionsInterface) {
var ok bool
o.options, ok = options.(*Options)
if !ok {
panic(fmt.Errorf("unexpected type %T", options))
}
}
func (o *Gitea) GetLogger() *gof3.Logger {
return o.GetOptions().GetLogger()
}
func (o *Gitea) Init(options gof3.OptionsInterface) {
o.SetOptions(options)
o.perPage = setting.ItemsPerPage
}
func (o *Gitea) GetDirectory() string {
return o.options.GetDirectory()
}
func (o *Gitea) GetDoer() *user_model.User {
return o.options.Doer
}
func (o *Gitea) GetNewMigrationHTTPClient() gof3.NewMigrationHTTPClientFun {
return migrations.NewMigrationHTTPClient
}
func (o *Gitea) SupportGetRepoComments() bool {
return false
}
func (o *Gitea) SetContext(ctx context.Context) {
o.ctx = ctx
}
func (o *Gitea) GetProvider(name string, parent common.ProviderInterface) common.ProviderInterface {
var parentImpl any
if parent != nil {
parentImpl = parent.GetImplementation()
}
switch name {
case driver.ProviderUser:
return driver.NewProvider[UserProvider, *UserProvider, User, *User, format.User, *format.User](&UserProvider{g: o})
case driver.ProviderProject:
return driver.NewProviderWithParentOne[ProjectProvider, *ProjectProvider, Project, *Project, format.Project, *format.Project, User, *User](&ProjectProvider{g: o})
case driver.ProviderMilestone:
return driver.NewProviderWithParentOneTwo[MilestoneProvider, *MilestoneProvider, Milestone, *Milestone, format.Milestone, *format.Milestone, User, *User, Project, *Project](&MilestoneProvider{g: o, project: parentImpl.(*ProjectProvider)})
case driver.ProviderIssue:
return driver.NewProviderWithParentOneTwo[IssueProvider, *IssueProvider, Issue, *Issue, format.Issue, *format.Issue, User, *User, Project, *Project](&IssueProvider{g: o, project: parentImpl.(*ProjectProvider)})
case driver.ProviderPullRequest:
return driver.NewProviderWithParentOneTwo[PullRequestProvider, *PullRequestProvider, PullRequest, *PullRequest, format.PullRequest, *format.PullRequest, User, *User, Project, *Project](&PullRequestProvider{g: o, project: parentImpl.(*ProjectProvider)})
case driver.ProviderReview:
return driver.NewProviderWithParentOneTwoThree[ReviewProvider, *ReviewProvider, Review, *Review, format.Review, *format.Review, User, *User, Project, *Project, PullRequest, *PullRequest](&ReviewProvider{g: o})
case driver.ProviderRepository:
return driver.NewProviderWithParentOneTwo[RepositoryProvider, *RepositoryProvider, Repository, *Repository, format.Repository, *format.Repository, User, *User, Project, *Project](&RepositoryProvider{g: o})
case driver.ProviderTopic:
return driver.NewProviderWithParentOneTwo[TopicProvider, *TopicProvider, Topic, *Topic, format.Topic, *format.Topic, User, *User, Project, *Project](&TopicProvider{g: o})
case driver.ProviderLabel:
return driver.NewProviderWithParentOneTwo[LabelProvider, *LabelProvider, Label, *Label, format.Label, *format.Label, User, *User, Project, *Project](&LabelProvider{g: o, project: parentImpl.(*ProjectProvider)})
case driver.ProviderRelease:
return driver.NewProviderWithParentOneTwo[ReleaseProvider, *ReleaseProvider, Release, *Release, format.Release, *format.Release, User, *User, Project, *Project](&ReleaseProvider{g: o})
case driver.ProviderAsset:
return driver.NewProviderWithParentOneTwoThree[AssetProvider, *AssetProvider, Asset, *Asset, format.ReleaseAsset, *format.ReleaseAsset, User, *User, Project, *Project, Release, *Release](&AssetProvider{g: o})
case driver.ProviderComment:
return driver.NewProviderWithParentOneTwoThreeInterface[CommentProvider, *CommentProvider, Comment, *Comment, format.Comment, *format.Comment, User, *User, Project, *Project](&CommentProvider{g: o})
case driver.ProviderReaction:
return driver.NewProviderWithParentOneTwoRest[ReactionProvider, *ReactionProvider, Reaction, *Reaction, format.Reaction, *format.Reaction, User, *User, Project, *Project](&ReactionProvider{g: o})
default:
panic(fmt.Sprintf("unknown provider name %s", name))
}
}
func (o Gitea) Finish() {
}

201
services/f3/driver/issue.go Normal file
View File

@ -0,0 +1,201 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/timeutil"
issue_service "code.gitea.io/gitea/services/issue"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type Issue struct {
issues_model.Issue
}
func IssueConverter(f *issues_model.Issue) *Issue {
return &Issue{
Issue: *f,
}
}
func (o Issue) GetID() int64 {
return o.Index
}
func (o *Issue) SetID(id int64) {
o.Index = id
}
func (o *Issue) IsNil() bool {
return o.Index == 0
}
func (o *Issue) Equals(other *Issue) bool {
return o.Index == other.Index
}
func (o *Issue) ToFormat() *format.Issue {
var milestone string
if o.Milestone != nil {
milestone = o.Milestone.Name
}
labels := make([]string, 0, len(o.Labels))
for _, label := range o.Labels {
labels = append(labels, label.Name)
}
var assignees []string
for i := range o.Assignees {
assignees = append(assignees, o.Assignees[i].Name)
}
return &format.Issue{
Common: format.Common{Index: o.Index},
Title: o.Title,
PosterID: o.Poster.ID,
Content: o.Content,
Milestone: milestone,
State: string(o.State()),
Created: o.CreatedUnix.AsTime(),
Updated: o.UpdatedUnix.AsTime(),
Closed: o.ClosedUnix.AsTimePtr(),
IsLocked: o.IsLocked,
Labels: labels,
Assignees: assignees,
}
}
func (o *Issue) FromFormat(issue *format.Issue) {
labels := make([]*issues_model.Label, 0, len(issue.Labels))
for _, label := range issue.Labels {
labels = append(labels, &issues_model.Label{Name: label})
}
assignees := make([]*user_model.User, 0, len(issue.Assignees))
for _, a := range issue.Assignees {
assignees = append(assignees, &user_model.User{
Name: a,
})
}
*o = Issue{
Issue: issues_model.Issue{
Title: issue.Title,
Index: issue.Index,
Poster: &user_model.User{
ID: issue.PosterID,
},
Content: issue.Content,
Milestone: &issues_model.Milestone{
Name: issue.Milestone,
},
IsClosed: issue.State == "closed",
CreatedUnix: timeutil.TimeStamp(issue.Created.Unix()),
UpdatedUnix: timeutil.TimeStamp(issue.Updated.Unix()),
ClosedUnix: timeutil.TimeStamp(issue.Closed.Unix()),
IsLocked: issue.IsLocked,
Labels: labels,
Assignees: assignees,
},
}
}
type IssueProvider struct {
g *Gitea
project *ProjectProvider
}
func (o *IssueProvider) GetReferences(issue *Issue) []common.Reference {
return nil
}
func (o *IssueProvider) ToFormat(issue *Issue) *format.Issue {
return issue.ToFormat()
}
func (o *IssueProvider) FromFormat(i *format.Issue) *Issue {
var issue Issue
issue.FromFormat(i)
if i.Milestone != "" {
issue.Milestone.ID = o.project.milestones.GetID(issue.Milestone.Name)
}
for _, label := range issue.Labels {
label.ID = o.project.labels.GetID(label.Name)
}
return &issue
}
func (o *IssueProvider) GetObjects(user *User, project *Project, page int) []*Issue {
issues, err := issues_model.Issues(o.g.ctx, &issues_model.IssuesOptions{
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
RepoID: project.GetID(),
})
if err != nil {
panic(fmt.Errorf("error while listing issues: %v", err))
}
return util.ConvertMap[*issues_model.Issue, *Issue](issues, IssueConverter)
}
func (o *IssueProvider) ProcessObject(user *User, project *Project, issue *Issue) {
if err := (&issue.Issue).LoadAttributes(o.g.ctx); err != nil {
panic(true)
}
}
func (o *IssueProvider) Get(user *User, project *Project, exemplar *Issue) *Issue {
id := exemplar.GetID()
issue, err := issues_model.GetIssueByIndex(project.GetID(), id)
if issues_model.IsErrIssueNotExist(err) {
return &Issue{}
}
if err != nil {
panic(err)
}
i := IssueConverter(issue)
o.ProcessObject(user, project, i)
return i
}
func (o *IssueProvider) Put(user *User, project *Project, issue *Issue) *Issue {
i := issue.Issue
i.RepoID = project.GetID()
labels := make([]int64, 0, len(i.Labels))
for _, label := range i.Labels {
labels = append(labels, label.ID)
}
if err := issues_model.NewIssue(&project.Repository, &i, labels, []string{}); err != nil {
panic(err)
}
return o.Get(user, project, IssueConverter(&i))
}
func (o *IssueProvider) Delete(user *User, project *Project, issue *Issue) *Issue {
m := o.Get(user, project, issue)
if !m.IsNil() {
repoPath := repo_model.RepoPath(user.Name, project.Name)
gitRepo, err := git.OpenRepository(o.g.ctx, repoPath)
if err != nil {
panic(err)
}
defer gitRepo.Close()
if err := issue_service.DeleteIssue(o.g.GetDoer(), gitRepo, &issue.Issue); err != nil {
panic(err)
}
}
return m
}

132
services/f3/driver/label.go Normal file
View File

@ -0,0 +1,132 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type Label struct {
issues_model.Label
}
func LabelConverter(f *issues_model.Label) *Label {
return &Label{
Label: *f,
}
}
func (o Label) GetID() int64 {
return o.ID
}
func (o Label) GetName() string {
return o.Name
}
func (o *Label) SetID(id int64) {
o.ID = id
}
func (o *Label) IsNil() bool {
return o.ID == 0
}
func (o *Label) Equals(other *Label) bool {
return o.Name == other.Name
}
func (o *Label) ToFormat() *format.Label {
return &format.Label{
Common: format.Common{Index: o.ID},
Name: o.Name,
Color: o.Color,
Description: o.Description,
}
}
func (o *Label) FromFormat(label *format.Label) {
*o = Label{
Label: issues_model.Label{
ID: label.Index,
Name: label.Name,
Description: label.Description,
Color: label.Color,
},
}
}
type LabelProvider struct {
g *Gitea
project *ProjectProvider
}
func (o *LabelProvider) GetReferences(label *Label) []common.Reference {
return nil
}
func (o *LabelProvider) ToFormat(label *Label) *format.Label {
return label.ToFormat()
}
func (o *LabelProvider) FromFormat(m *format.Label) *Label {
var label Label
label.FromFormat(m)
return &label
}
func (o *LabelProvider) GetObjects(user *User, project *Project, page int) []*Label {
labels, err := issues_model.GetLabelsByRepoID(o.g.ctx, project.GetID(), "", db.ListOptions{Page: page, PageSize: o.g.perPage})
if err != nil {
panic(fmt.Errorf("error while listing labels: %v", err))
}
r := util.ConvertMap[*issues_model.Label, *Label](labels, LabelConverter)
if o.project != nil {
o.project.labels = util.NewNameIDMap[*Label](r)
}
return r
}
func (o *LabelProvider) ProcessObject(user *User, project *Project, label *Label) {
}
func (o *LabelProvider) Get(user *User, project *Project, exemplar *Label) *Label {
id := exemplar.GetID()
label, err := issues_model.GetLabelInRepoByID(o.g.ctx, project.GetID(), id)
if issues_model.IsErrRepoLabelNotExist(err) {
return &Label{}
}
if err != nil {
panic(err)
}
return LabelConverter(label)
}
func (o *LabelProvider) Put(user *User, project *Project, label *Label) *Label {
l := label.Label
l.RepoID = project.GetID()
if err := issues_model.NewLabel(o.g.ctx, &l); err != nil {
panic(err)
}
return o.Get(user, project, LabelConverter(&l))
}
func (o *LabelProvider) Delete(user *User, project *Project, label *Label) *Label {
l := o.Get(user, project, label)
if !l.IsNil() {
if err := issues_model.DeleteLabel(project.GetID(), l.GetID()); err != nil {
panic(err)
}
}
return l
}

View File

@ -0,0 +1,179 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"fmt"
"time"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type Milestone struct {
issues_model.Milestone
}
func MilestoneConverter(f *issues_model.Milestone) *Milestone {
return &Milestone{
Milestone: *f,
}
}
func (o Milestone) GetID() int64 {
return o.ID
}
func (o Milestone) GetName() string {
return o.Name
}
func (o *Milestone) SetID(id int64) {
o.ID = id
}
func (o *Milestone) IsNil() bool {
return o.ID == 0
}
func (o *Milestone) Equals(other *Milestone) bool {
return o.Name == other.Name
}
func (o *Milestone) ToFormat() *format.Milestone {
milestone := &format.Milestone{
Common: format.Common{Index: o.ID},
Title: o.Name,
Description: o.Content,
Created: o.CreatedUnix.AsTime(),
Updated: o.UpdatedUnix.AsTimePtr(),
State: string(o.State()),
}
if o.IsClosed {
milestone.Closed = o.ClosedDateUnix.AsTimePtr()
}
if o.DeadlineUnix.Year() < 9999 {
milestone.Deadline = o.DeadlineUnix.AsTimePtr()
}
return milestone
}
func (o *Milestone) FromFormat(milestone *format.Milestone) {
var deadline timeutil.TimeStamp
if milestone.Deadline != nil {
deadline = timeutil.TimeStamp(milestone.Deadline.Unix())
}
if deadline == 0 {
deadline = timeutil.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.DefaultUILocation).Unix())
}
var closed timeutil.TimeStamp
if milestone.Closed != nil {
closed = timeutil.TimeStamp(milestone.Closed.Unix())
}
if milestone.Created.IsZero() {
if milestone.Updated != nil {
milestone.Created = *milestone.Updated
} else if milestone.Deadline != nil {
milestone.Created = *milestone.Deadline
} else {
milestone.Created = time.Now()
}
}
if milestone.Updated == nil || milestone.Updated.IsZero() {
milestone.Updated = &milestone.Created
}
*o = Milestone{
issues_model.Milestone{
ID: milestone.Index,
Name: milestone.Title,
Content: milestone.Description,
IsClosed: milestone.State == "closed",
CreatedUnix: timeutil.TimeStamp(milestone.Created.Unix()),
UpdatedUnix: timeutil.TimeStamp(milestone.Updated.Unix()),
ClosedDateUnix: closed,
DeadlineUnix: deadline,
},
}
}
type MilestoneProvider struct {
g *Gitea
project *ProjectProvider
}
func (o *MilestoneProvider) GetReferences(milestone *Milestone) []common.Reference {
return nil
}
func (o *MilestoneProvider) ToFormat(milestone *Milestone) *format.Milestone {
return milestone.ToFormat()
}
func (o *MilestoneProvider) FromFormat(m *format.Milestone) *Milestone {
var milestone Milestone
milestone.FromFormat(m)
return &milestone
}
func (o *MilestoneProvider) GetObjects(user *User, project *Project, page int) []*Milestone {
milestones, _, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
RepoID: project.GetID(),
State: api.StateAll,
})
if err != nil {
panic(fmt.Errorf("error while listing milestones: %v", err))
}
r := util.ConvertMap[*issues_model.Milestone, *Milestone](([]*issues_model.Milestone)(milestones), MilestoneConverter)
if o.project != nil {
o.project.milestones = util.NewNameIDMap[*Milestone](r)
}
return r
}
func (o *MilestoneProvider) ProcessObject(user *User, project *Project, milestone *Milestone) {
}
func (o *MilestoneProvider) Get(user *User, project *Project, exemplar *Milestone) *Milestone {
id := exemplar.GetID()
milestone, err := issues_model.GetMilestoneByRepoID(o.g.ctx, project.GetID(), id)
if issues_model.IsErrMilestoneNotExist(err) {
return &Milestone{}
}
if err != nil {
panic(err)
}
return MilestoneConverter(milestone)
}
func (o *MilestoneProvider) Put(user *User, project *Project, milestone *Milestone) *Milestone {
m := milestone.Milestone
m.RepoID = project.GetID()
if err := issues_model.NewMilestone(&m); err != nil {
panic(err)
}
return o.Get(user, project, MilestoneConverter(&m))
}
func (o *MilestoneProvider) Delete(user *User, project *Project, milestone *Milestone) *Milestone {
m := o.Get(user, project, milestone)
if !m.IsNil() {
if err := issues_model.DeleteMilestoneByRepoID(project.GetID(), m.GetID()); err != nil {
panic(err)
}
}
return m
}

View File

@ -0,0 +1,163 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"fmt"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
repo_module "code.gitea.io/gitea/modules/repository"
repo_service "code.gitea.io/gitea/services/repository"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
f3_util "lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type Project struct {
repo_model.Repository
}
func ProjectConverter(f *repo_model.Repository) *Project {
return &Project{
Repository: *f,
}
}
func (o Project) GetID() int64 {
return o.ID
}
func (o *Project) SetID(id int64) {
o.ID = id
}
func (o *Project) IsNil() bool {
return o.ID == 0
}
func (o *Project) Equals(other *Project) bool {
return (o.Name == other.Name)
}
func (o *Project) ToFormat() *format.Project {
return &format.Project{
Common: format.Common{Index: o.ID},
Name: o.Name,
Owner: o.Owner.Name,
IsPrivate: o.IsPrivate,
Description: o.Description,
CloneURL: repo_model.ComposeHTTPSCloneURL(o.Owner.Name, o.Name),
OriginalURL: o.OriginalURL,
DefaultBranch: o.DefaultBranch,
}
}
func (o *Project) FromFormat(project *format.Project) {
*o = Project{
Repository: repo_model.Repository{
ID: project.Index,
Name: project.Name,
Owner: &user_model.User{
Name: project.Owner,
},
IsPrivate: project.IsPrivate,
Description: project.Description,
OriginalURL: project.OriginalURL,
DefaultBranch: project.DefaultBranch,
},
}
}
type ProjectProvider struct {
g *Gitea
milestones f3_util.NameIDMap
labels f3_util.NameIDMap
}
func (o *ProjectProvider) GetReferences(project *Project) []common.Reference {
return nil
}
func (o *ProjectProvider) ToFormat(project *Project) *format.Project {
return project.ToFormat()
}
func (o *ProjectProvider) FromFormat(p *format.Project) *Project {
var project Project
project.FromFormat(p)
return &project
}
func (o *ProjectProvider) GetObjects(user *User, page int) []*Project {
repoList, _, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
Actor: &user.User,
Private: true,
})
if err != nil {
panic(fmt.Errorf("error while listing projects: %T %v", err, err))
}
if err := repoList.LoadAttributes(); err != nil {
panic(nil)
}
return f3_util.ConvertMap[*repo_model.Repository, *Project](([]*repo_model.Repository)(repoList), ProjectConverter)
}
func (o *ProjectProvider) ProcessObject(user *User, project *Project) {
}
func (o *ProjectProvider) Get(user *User, exemplar *Project) *Project {
var project *repo_model.Repository
var err error
if exemplar.GetID() > 0 {
project, err = repo_model.GetRepositoryByIDCtx(o.g.ctx, exemplar.GetID())
} else if exemplar.Name != "" {
project, err = repo_model.GetRepositoryByName(user.GetID(), exemplar.Name)
} else {
panic("GetID() == 0 and ProjectName == \"\"")
}
if repo_model.IsErrRepoNotExist(err) {
return &Project{}
}
if err != nil {
panic(fmt.Errorf("project %v %w", exemplar, err))
}
if err := project.GetOwner(o.g.ctx); err != nil {
panic(err)
}
return ProjectConverter(project)
}
func (o *ProjectProvider) Put(user *User, project *Project) *Project {
repo, err := repo_module.CreateRepository(o.g.GetDoer(), &user.User, repo_module.CreateRepoOptions{
Name: project.Name,
Description: project.Description,
OriginalURL: project.OriginalURL,
IsPrivate: project.IsPrivate,
})
if err != nil {
panic(err)
}
return o.Get(user, ProjectConverter(repo))
}
func (o *ProjectProvider) Delete(user *User, project *Project) *Project {
if project.IsNil() {
return project
}
if project.ID > 0 {
project = o.Get(user, project)
}
if !project.IsNil() {
err := repo_service.DeleteRepository(o.g.ctx, o.g.GetDoer(), &project.Repository, true)
if err != nil {
panic(err)
}
}
return project
}

View File

@ -0,0 +1,314 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"context"
"fmt"
"time"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
issue_service "code.gitea.io/gitea/services/issue"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
f3_gitea "lab.forgefriends.org/friendlyforgeformat/gof3/forges/gitea"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type PullRequest struct {
issues_model.PullRequest
FetchFunc func(repository string) string
}
func PullRequestConverter(f *issues_model.PullRequest) *PullRequest {
return &PullRequest{
PullRequest: *f,
}
}
func (o PullRequest) GetID() int64 {
return o.Index
}
func (o *PullRequest) SetID(id int64) {
o.Index = id
}
func (o *PullRequest) IsNil() bool {
return o.Index == 0
}
func (o *PullRequest) Equals(other *PullRequest) bool {
return o.Issue.Title == other.Issue.Title
}
func (o PullRequest) IsForkPullRequest() bool {
return o.HeadRepoID != o.BaseRepoID
}
func (o *PullRequest) ToFormat() *format.PullRequest {
var milestone string
if o.Issue.Milestone != nil {
milestone = o.Issue.Milestone.Name
}
labels := make([]string, 0, len(o.Issue.Labels))
for _, label := range o.Issue.Labels {
labels = append(labels, label.Name)
}
var mergedTime *time.Time
if o.HasMerged {
mergedTime = o.MergedUnix.AsTimePtr()
}
getSHA := func(repo *repo_model.Repository, branch string) string {
r, err := git.OpenRepository(context.Background(), repo.RepoPath())
if err != nil {
panic(err)
}
defer r.Close()
b, err := r.GetBranch(branch)
if err != nil {
panic(err)
}
c, err := b.GetCommit()
if err != nil {
panic(err)
}
return c.ID.String()
}
head := format.PullRequestBranch{
CloneURL: o.HeadRepo.CloneLink().HTTPS,
Ref: o.HeadBranch,
SHA: getSHA(o.HeadRepo, o.HeadBranch),
RepoName: o.HeadRepo.Name,
OwnerName: o.HeadRepo.OwnerName,
}
base := format.PullRequestBranch{
CloneURL: o.BaseRepo.CloneLink().HTTPS,
Ref: o.BaseBranch,
SHA: getSHA(o.BaseRepo, o.BaseBranch),
RepoName: o.BaseRepo.Name,
OwnerName: o.BaseRepo.OwnerName,
}
return &format.PullRequest{
Common: format.Common{Index: o.Index},
PosterID: o.Issue.Poster.ID,
Title: o.Issue.Title,
Content: o.Issue.Content,
Milestone: milestone,
State: string(o.Issue.State()),
IsLocked: o.Issue.IsLocked,
Created: o.Issue.CreatedUnix.AsTime(),
Updated: o.Issue.UpdatedUnix.AsTime(),
Closed: o.Issue.ClosedUnix.AsTimePtr(),
Labels: labels,
PatchURL: o.Issue.PatchURL(),
Merged: o.HasMerged,
MergedTime: mergedTime,
MergeCommitSHA: o.MergedCommitID,
Head: head,
Base: base,
}
}
func (o *PullRequest) FromFormat(pullRequest *format.PullRequest) {
labels := make([]*issues_model.Label, 0, len(pullRequest.Labels))
for _, label := range pullRequest.Labels {
labels = append(labels, &issues_model.Label{Name: label})
}
if pullRequest.Created.IsZero() {
if pullRequest.Closed != nil {
pullRequest.Created = *pullRequest.Closed
} else if pullRequest.MergedTime != nil {
pullRequest.Created = *pullRequest.MergedTime
} else {
pullRequest.Created = time.Now()
}
}
if pullRequest.Updated.IsZero() {
pullRequest.Updated = pullRequest.Created
}
base, err := repo_model.GetRepositoryByOwnerAndName(pullRequest.Base.OwnerName, pullRequest.Base.RepoName)
if err != nil {
panic(err)
}
var head *repo_model.Repository
if pullRequest.Head.RepoName == "" {
head = base
} else {
head, err = repo_model.GetRepositoryByOwnerAndName(pullRequest.Head.OwnerName, pullRequest.Head.RepoName)
if err != nil {
panic(err)
}
}
issue := issues_model.Issue{
RepoID: base.ID,
Repo: base,
Title: pullRequest.Title,
Index: pullRequest.Index,
Content: pullRequest.Content,
IsPull: true,
IsClosed: pullRequest.State == "closed",
IsLocked: pullRequest.IsLocked,
Labels: labels,
CreatedUnix: timeutil.TimeStamp(pullRequest.Created.Unix()),
UpdatedUnix: timeutil.TimeStamp(pullRequest.Updated.Unix()),
}
pr := issues_model.PullRequest{
HeadRepoID: head.ID,
HeadBranch: pullRequest.Head.Ref,
BaseRepoID: base.ID,
BaseBranch: pullRequest.Base.Ref,
MergeBase: pullRequest.Base.SHA,
Index: pullRequest.Index,
HasMerged: pullRequest.Merged,
Issue: &issue,
}
if pr.Issue.IsClosed && pullRequest.Closed != nil {
pr.Issue.ClosedUnix = timeutil.TimeStamp(pullRequest.Closed.Unix())
}
if pr.HasMerged && pullRequest.MergedTime != nil {
pr.MergedUnix = timeutil.TimeStamp(pullRequest.MergedTime.Unix())
pr.MergedCommitID = pullRequest.MergeCommitSHA
}
*o = PullRequest{
PullRequest: pr,
}
}
type PullRequestProvider struct {
g *Gitea
project *ProjectProvider
prHeadCache f3_gitea.PrHeadCache
}
func (o *PullRequestProvider) GetReferences(pullRequest *PullRequest) []common.Reference {
return nil
}
func (o *PullRequestProvider) ToFormat(pullRequest *PullRequest) *format.PullRequest {
return pullRequest.ToFormat()
}
func (o *PullRequestProvider) FromFormat(pr *format.PullRequest) *PullRequest {
var pullRequest PullRequest
pullRequest.FromFormat(pr)
return &pullRequest
}
func (o *PullRequestProvider) Init() *PullRequestProvider {
o.prHeadCache = make(f3_gitea.PrHeadCache)
return o
}
func (o *PullRequestProvider) cleanupRemotes(repository string) {
for remote := range o.prHeadCache {
util.Command(o.g.ctx, "git", "-C", repository, "remote", "rm", remote)
}
o.prHeadCache = make(f3_gitea.PrHeadCache)
}
func (o *PullRequestProvider) GetObjects(user *User, project *Project, page int) []*PullRequest {
pullRequests, _, err := issues_model.PullRequests(project.GetID(), &issues_model.PullRequestsOptions{
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
State: string(api.StateAll),
})
if err != nil {
panic(fmt.Errorf("error while listing pullRequests: %v", err))
}
return util.ConvertMap[*issues_model.PullRequest, *PullRequest](pullRequests, PullRequestConverter)
}
func (o *PullRequestProvider) ProcessObject(user *User, project *Project, pr *PullRequest) {
if err := pr.LoadIssue(o.g.ctx); err != nil {
panic(err)
}
if err := pr.Issue.LoadRepo(o.g.ctx); err != nil {
panic(err)
}
if err := pr.LoadAttributes(o.g.ctx); err != nil {
panic(err)
}
if err := pr.LoadBaseRepo(o.g.ctx); err != nil {
panic(err)
}
if err := pr.LoadHeadRepo(o.g.ctx); err != nil {
panic(err)
}
pr.FetchFunc = func(repository string) string {
head, messages := f3_gitea.UpdateGitForPullRequest(o.g.ctx, &o.prHeadCache, pr.ToFormat(), repository)
for _, message := range messages {
o.g.GetLogger().Warn(message)
}
o.cleanupRemotes(repository)
return head
}
}
func (o *PullRequestProvider) Get(user *User, project *Project, pullRequest *PullRequest) *PullRequest {
id := pullRequest.GetID()
pr, err := issues_model.GetPullRequestByIndex(o.g.ctx, project.GetID(), id)
if issues_model.IsErrPullRequestNotExist(err) {
return &PullRequest{}
}
if err != nil {
panic(err)
}
p := PullRequestConverter(pr)
o.ProcessObject(user, project, p)
return p
}
func (o *PullRequestProvider) Put(user *User, project *Project, pullRequest *PullRequest) *PullRequest {
i := pullRequest.PullRequest.Issue
i.RepoID = project.GetID()
labels := make([]int64, 0, len(i.Labels))
for _, label := range i.Labels {
labels = append(labels, label.ID)
}
if err := issues_model.NewPullRequest(o.g.ctx, &project.Repository, i, labels, []string{}, &pullRequest.PullRequest); err != nil {
panic(err)
}
return o.Get(user, project, pullRequest)
}
func (o *PullRequestProvider) Delete(user *User, project *Project, pullRequest *PullRequest) *PullRequest {
p := o.Get(user, project, pullRequest)
if !p.IsNil() {
repoPath := repo_model.RepoPath(user.Name, project.Name)
gitRepo, err := git.OpenRepository(o.g.ctx, repoPath)
if err != nil {
panic(err)
}
defer gitRepo.Close()
if err := issue_service.DeleteIssue(o.g.GetDoer(), gitRepo, p.PullRequest.Issue); err != nil {
panic(err)
}
}
return p
}

View File

@ -0,0 +1,180 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
"xorm.io/builder"
)
type Reaction struct {
issues_model.Reaction
}
func ReactionConverter(f *issues_model.Reaction) *Reaction {
return &Reaction{
Reaction: *f,
}
}
func (o Reaction) GetID() int64 {
return o.ID
}
func (o *Reaction) SetID(id int64) {
o.ID = id
}
func (o *Reaction) IsNil() bool {
return o.ID == 0
}
func (o *Reaction) Equals(other *Reaction) bool {
return o.UserID == other.UserID && o.Type == other.Type
}
func (o *Reaction) ToFormat() *format.Reaction {
return &format.Reaction{
Common: format.Common{Index: o.ID},
UserID: o.User.ID,
Content: o.Type,
}
}
func (o *Reaction) FromFormat(reaction *format.Reaction) {
*o = Reaction{
Reaction: issues_model.Reaction{
ID: reaction.GetID(),
UserID: reaction.UserID,
User: &user_model.User{
ID: reaction.UserID,
},
Type: reaction.Content,
},
}
}
type ReactionProvider struct {
g *Gitea
}
func (o *ReactionProvider) GetReferences(reaction *Reaction) []common.Reference {
return nil
}
func (o *ReactionProvider) ToFormat(reaction *Reaction) *format.Reaction {
return reaction.ToFormat()
}
func (o *ReactionProvider) FromFormat(m *format.Reaction) *Reaction {
var reaction Reaction
reaction.FromFormat(m)
return &reaction
}
//
// Although it would be possible to use a higher level logic instead of the database,
// as of September 2022 (1.18 dev)
// (i) models/issues/reaction.go imposes a significant overhead
// (ii) is fragile and bugous https://github.com/go-gitea/gitea/issues/20860
//
func (o *ReactionProvider) GetObjects(user *User, project *Project, parents []common.ContainerObjectInterface, page int) []*Reaction {
cond := builder.NewCond()
switch l := parents[len(parents)-1].(type) {
case *Issue:
cond = cond.And(builder.Eq{"reaction.issue_id": l.ID})
cond = cond.And(builder.Eq{"reaction.comment_id": 0})
case *Comment:
cond = cond.And(builder.Eq{"reaction.comment_id": l.ID})
default:
panic(fmt.Errorf("unexpected type %T", parents[len(parents)-1]))
}
sess := db.GetEngine(o.g.ctx).Where(cond)
if page > 0 {
sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: o.g.perPage})
}
reactions := make([]*issues_model.Reaction, 0, 10)
if err := sess.Find(&reactions); err != nil {
panic(err)
}
_, err := (issues_model.ReactionList)(reactions).LoadUsers(o.g.ctx, nil)
if err != nil {
panic(err)
}
return util.ConvertMap[*issues_model.Reaction, *Reaction](reactions, ReactionConverter)
}
func (o *ReactionProvider) ProcessObject(user *User, project *Project, parents []common.ContainerObjectInterface, reaction *Reaction) {
}
func (o *ReactionProvider) Get(user *User, project *Project, parents []common.ContainerObjectInterface, exemplar *Reaction) *Reaction {
reaction := &Reaction{}
has, err := db.GetEngine(o.g.ctx).ID(exemplar.GetID()).Get(&reaction.Reaction)
if err != nil {
panic(err)
} else if !has {
return &Reaction{}
}
if _, err := (issues_model.ReactionList{&reaction.Reaction}).LoadUsers(o.g.ctx, nil); err != nil {
panic(err)
}
return reaction
}
func (o *ReactionProvider) Put(user *User, project *Project, parents []common.ContainerObjectInterface, reaction *Reaction) *Reaction {
r := &issues_model.Reaction{
Type: reaction.Type,
UserID: o.g.GetDoer().ID,
}
switch l := parents[len(parents)-1].(type) {
case *Issue:
r.IssueID = l.ID
r.CommentID = 0
case *Comment:
i, ok := parents[len(parents)-2].(*Issue)
if !ok {
panic(fmt.Errorf("unexpected type %T", parents[len(parents)-2]))
}
r.IssueID = i.ID
r.CommentID = l.ID
default:
panic(fmt.Errorf("unexpected type %T", parents[len(parents)-1]))
}
ctx, committer, err := db.TxContext(o.g.ctx)
if err != nil {
panic(err)
}
defer committer.Close()
if _, err := db.GetEngine(ctx).Insert(r); err != nil {
panic(err)
}
if err := committer.Commit(); err != nil {
panic(err)
}
return ReactionConverter(r)
}
func (o *ReactionProvider) Delete(user *User, project *Project, parents []common.ContainerObjectInterface, reaction *Reaction) *Reaction {
r := o.Get(user, project, parents, reaction)
if !r.IsNil() {
if _, err := db.GetEngine(o.g.ctx).Delete(&reaction.Reaction); err != nil {
panic(err)
}
return reaction
}
return r
}

View File

@ -0,0 +1,168 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"fmt"
"strings"
"time"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/timeutil"
release_service "code.gitea.io/gitea/services/release"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type Release struct {
repo_model.Release
}
func ReleaseConverter(f *repo_model.Release) *Release {
return &Release{
Release: *f,
}
}
func (o Release) GetID() int64 {
return o.ID
}
func (o *Release) SetID(id int64) {
o.ID = id
}
func (o *Release) IsNil() bool {
return o.ID == 0
}
func (o *Release) Equals(other *Release) bool {
return o.ID == other.ID
}
func (o *Release) ToFormat() *format.Release {
return &format.Release{
Common: format.Common{Index: o.ID},
TagName: o.TagName,
TargetCommitish: o.Target,
Name: o.Title,
Body: o.Note,
Draft: o.IsDraft,
Prerelease: o.IsPrerelease,
Created: o.CreatedUnix.AsTime(),
PublisherID: o.Publisher.ID,
}
}
func (o *Release) FromFormat(release *format.Release) {
if release.Created.IsZero() {
if !release.Published.IsZero() {
release.Created = release.Published
} else {
release.Created = time.Now()
}
}
*o = Release{
repo_model.Release{
PublisherID: release.PublisherID,
Publisher: &user_model.User{
ID: release.PublisherID,
},
TagName: release.TagName,
LowerTagName: strings.ToLower(release.TagName),
Target: release.TargetCommitish,
Title: release.Name,
Note: release.Body,
IsDraft: release.Draft,
IsPrerelease: release.Prerelease,
IsTag: false,
CreatedUnix: timeutil.TimeStamp(release.Created.Unix()),
},
}
}
type ReleaseProvider struct {
g *Gitea
}
func (o *ReleaseProvider) GetReferences(release *Release) []common.Reference {
return nil
}
func (o *ReleaseProvider) ToFormat(release *Release) *format.Release {
return release.ToFormat()
}
func (o *ReleaseProvider) FromFormat(i *format.Release) *Release {
var release Release
release.FromFormat(i)
return &release
}
func (o *ReleaseProvider) GetObjects(user *User, project *Project, page int) []*Release {
releases, err := repo_model.GetReleasesByRepoID(o.g.ctx, project.GetID(), repo_model.FindReleasesOptions{
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
IncludeDrafts: true,
IncludeTags: false,
})
if err != nil {
panic(fmt.Errorf("error while listing releases: %v", err))
}
return util.ConvertMap[*repo_model.Release, *Release](releases, ReleaseConverter)
}
func (o *ReleaseProvider) ProcessObject(user *User, project *Project, release *Release) {
if err := (&release.Release).LoadAttributes(o.g.ctx); err != nil {
panic(err)
}
}
func (o *ReleaseProvider) Get(user *User, project *Project, exemplar *Release) *Release {
id := exemplar.GetID()
release, err := repo_model.GetReleaseByID(o.g.ctx, id)
if repo_model.IsErrReleaseNotExist(err) {
return &Release{}
}
if err != nil {
panic(err)
}
r := ReleaseConverter(release)
o.ProcessObject(user, project, r)
return r
}
func (o *ReleaseProvider) Put(user *User, project *Project, release *Release) *Release {
r := release.Release
r.RepoID = project.GetID()
repoPath := repo_model.RepoPath(user.Name, project.Name)
gitRepo, err := git.OpenRepository(o.g.ctx, repoPath)
if err != nil {
panic(err)
}
defer gitRepo.Close()
if err := release_service.CreateRelease(gitRepo, &r, nil, ""); err != nil {
panic(err)
}
return o.Get(user, project, ReleaseConverter(&r))
}
func (o *ReleaseProvider) Delete(user *User, project *Project, release *Release) *Release {
m := o.Get(user, project, release)
if !m.IsNil() {
if err := release_service.DeleteReleaseByID(o.g.ctx, release.GetID(), o.g.GetDoer(), false); err != nil {
panic(err)
}
}
return m
}

View File

@ -0,0 +1,107 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
repo_model "code.gitea.io/gitea/models/repo"
base "code.gitea.io/gitea/modules/migration"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/services/migrations"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type Repository struct {
format.Repository
}
func (o *Repository) Equals(other *Repository) bool {
return false // it is costly to figure that out, mirroring is as fast
}
func (o *Repository) ToFormat() *format.Repository {
return &o.Repository
}
func (o *Repository) FromFormat(repository *format.Repository) {
o.Repository = *repository
}
type RepositoryProvider struct {
g *Gitea
}
func (o *RepositoryProvider) GetReferences(repository *Repository) []common.Reference {
return nil
}
func (o *RepositoryProvider) ToFormat(repository *Repository) *format.Repository {
return repository.ToFormat()
}
func (o *RepositoryProvider) FromFormat(p *format.Repository) *Repository {
var repository Repository
repository.FromFormat(p)
return &repository
}
func (o *RepositoryProvider) GetObjects(user *User, project *Project, page int) []*Repository {
if page > 0 {
return make([]*Repository, 0)
}
repositories := make([]*Repository, len(format.RepositoryNames))
for _, name := range format.RepositoryNames {
repositories = append(repositories, o.Get(user, project, &Repository{
Repository: format.Repository{
Name: name,
},
}))
}
return repositories
}
func (o *RepositoryProvider) ProcessObject(user *User, project *Project, repository *Repository) {
}
func (o *RepositoryProvider) Get(user *User, project *Project, exemplar *Repository) *Repository {
repoPath := repo_model.RepoPath(user.Name, project.Name) + exemplar.Name
return &Repository{
Repository: format.Repository{
Name: exemplar.Name,
FetchFunc: func(destination string) {
util.Command(o.g.ctx, "git", "clone", "--bare", repoPath, destination)
},
},
}
}
func (o *RepositoryProvider) Put(user *User, project *Project, repository *Repository) *Repository {
if repository.FetchFunc != nil {
directory, delete := format.RepositoryDefaultDirectory()
defer delete()
repository.FetchFunc(directory)
_, err := repo_module.MigrateRepositoryGitData(o.g.ctx, &user.User, &project.Repository, base.MigrateOptions{
RepoName: project.Name,
Mirror: false,
MirrorInterval: "",
LFS: false,
LFSEndpoint: "",
CloneAddr: directory,
Wiki: o.g.GetOptions().GetFeatures().Wiki,
Releases: o.g.GetOptions().GetFeatures().Releases,
}, migrations.NewMigrationHTTPTransport())
if err != nil {
panic(err)
}
}
return o.Get(user, project, repository)
}
func (o *RepositoryProvider) Delete(user *User, project *Project, repository *Repository) *Repository {
panic("It is not possible to delete a repository")
}

View File

@ -0,0 +1,220 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"fmt"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/timeutil"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type Review struct {
issues_model.Review
}
func ReviewConverter(f *issues_model.Review) *Review {
return &Review{
Review: *f,
}
}
func (o Review) GetID() int64 {
return o.ID
}
func (o *Review) SetID(id int64) {
o.ID = id
}
func (o *Review) IsNil() bool {
return o.ID == 0
}
func (o *Review) Equals(other *Review) bool {
return o.Content == other.Content
}
func (o *Review) ToFormat() *format.Review {
comments := make([]*format.ReviewComment, 0, len(o.Comments))
for _, comment := range o.Comments {
comments = append(comments, &format.ReviewComment{
Common: format.Common{Index: comment.ID},
// InReplyTo
Content: comment.Content,
TreePath: comment.TreePath,
DiffHunk: convert.Patch2diff(comment.Patch),
Patch: comment.Patch,
Line: int(comment.Line),
CommitID: comment.CommitSHA,
PosterID: comment.PosterID,
CreatedAt: comment.CreatedUnix.AsTime(),
UpdatedAt: comment.UpdatedUnix.AsTime(),
})
}
review := format.Review{
Common: format.Common{Index: o.Review.ID},
IssueIndex: o.IssueID,
Official: o.Review.Official,
CommitID: o.Review.CommitID,
Content: o.Review.Content,
CreatedAt: o.Review.CreatedUnix.AsTime(),
State: format.ReviewStateUnknown,
Comments: comments,
}
if o.Review.Reviewer != nil {
review.ReviewerID = o.Review.Reviewer.ID
}
switch o.Type {
case issues_model.ReviewTypeApprove:
review.State = format.ReviewStateApproved
case issues_model.ReviewTypeReject:
review.State = format.ReviewStateChangesRequested
case issues_model.ReviewTypeComment:
review.State = format.ReviewStateCommented
case issues_model.ReviewTypePending:
review.State = format.ReviewStatePending
case issues_model.ReviewTypeRequest:
review.State = format.ReviewStateRequestReview
}
return &review
}
func (o *Review) FromFormat(review *format.Review) {
comments := make([]*issues_model.Comment, 0, len(review.Comments))
for _, comment := range review.Comments {
comments = append(comments, &issues_model.Comment{
ID: comment.GetID(),
Type: issues_model.CommentTypeReview,
// InReplyTo
CommitSHA: comment.CommitID,
Line: int64(comment.Line),
TreePath: comment.TreePath,
Content: comment.Content,
Patch: comment.Patch,
PosterID: comment.PosterID,
CreatedUnix: timeutil.TimeStamp(comment.CreatedAt.Unix()),
UpdatedUnix: timeutil.TimeStamp(comment.UpdatedAt.Unix()),
})
}
*o = Review{
Review: issues_model.Review{
ID: review.GetID(),
Reviewer: &user_model.User{
ID: review.ReviewerID,
},
IssueID: review.IssueIndex,
Official: review.Official,
CommitID: review.CommitID,
Content: review.Content,
CreatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
Comments: comments,
},
}
switch review.State {
case format.ReviewStateApproved:
o.Type = issues_model.ReviewTypeApprove
case format.ReviewStateChangesRequested:
o.Type = issues_model.ReviewTypeReject
case format.ReviewStateCommented:
o.Type = issues_model.ReviewTypeComment
case format.ReviewStatePending:
o.Type = issues_model.ReviewTypePending
case format.ReviewStateRequestReview:
o.Type = issues_model.ReviewTypeRequest
}
}
type ReviewProvider struct {
g *Gitea
}
func (o *ReviewProvider) GetReferences(review *Review) []common.Reference {
return nil
}
func (o *ReviewProvider) ToFormat(review *Review) *format.Review {
return review.ToFormat()
}
func (o *ReviewProvider) FromFormat(i *format.Review) *Review {
var review Review
review.FromFormat(i)
return &review
}
func (o *ReviewProvider) GetObjects(user *User, project *Project, pullRequest *PullRequest, page int) []*Review {
reviews, err := issues_model.FindReviews(o.g.ctx, issues_model.FindReviewOptions{
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
IssueID: pullRequest.IssueID,
})
if err != nil {
panic(fmt.Errorf("error while listing reviews: %v", err))
}
return util.ConvertMap[*issues_model.Review, *Review](reviews, ReviewConverter)
}
func (o *ReviewProvider) ProcessObject(user *User, project *Project, pullRequest *PullRequest, review *Review) {
if err := (&review.Review).LoadAttributes(o.g.ctx); err != nil {
panic(err)
}
}
func (o *ReviewProvider) Get(user *User, project *Project, pullRequest *PullRequest, exemplar *Review) *Review {
id := exemplar.GetID()
review, err := issues_model.GetReviewByID(o.g.ctx, id)
if issues_model.IsErrReviewNotExist(err) {
return &Review{}
}
if err != nil {
panic(err)
}
if err := review.LoadAttributes(o.g.ctx); err != nil {
panic(err)
}
return ReviewConverter(review)
}
func (o *ReviewProvider) Put(user *User, project *Project, pullRequest *PullRequest, review *Review) *Review {
r := &review.Review
r.ID = 0
for _, comment := range r.Comments {
comment.ID = 0
}
r.IssueID = pullRequest.IssueID
// this is a workaround because the id remapping logic is missing, wait until it is implemented and remove it
u, err := user_model.GetUserByName(o.g.ctx, r.Reviewer.Name)
if err != nil {
panic(err)
}
r.ReviewerID = u.ID
if err := issues_model.InsertReviews([]*issues_model.Review{r}); err != nil {
panic(err)
}
return o.Get(user, project, pullRequest, ReviewConverter(r))
}
func (o *ReviewProvider) Delete(user *User, project *Project, pullRequest *PullRequest, review *Review) *Review {
r := o.Get(user, project, pullRequest, review)
if !r.IsNil() {
if err := issues_model.DeleteReview(&r.Review); err != nil {
panic(err)
}
}
return r
}

121
services/f3/driver/topic.go Normal file
View File

@ -0,0 +1,121 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
"lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type Topic struct {
repo_model.Topic
}
func TopicConverter(f *repo_model.Topic) *Topic {
return &Topic{
Topic: *f,
}
}
func (o Topic) GetID() int64 {
return o.ID
}
func (o *Topic) SetID(id int64) {
o.ID = id
}
func (o *Topic) IsNil() bool {
return o.ID == 0
}
func (o *Topic) Equals(other *Topic) bool {
return o.Name == other.Name
}
func (o *Topic) ToFormat() *format.Topic {
return &format.Topic{
Common: format.Common{Index: o.ID},
Name: o.Name,
}
}
func (o *Topic) FromFormat(topic *format.Topic) {
*o = Topic{
Topic: repo_model.Topic{
ID: topic.Index,
Name: topic.Name,
},
}
}
type TopicProvider struct {
g *Gitea
}
func (o *TopicProvider) GetReferences(topic *Topic) []common.Reference {
return nil
}
func (o *TopicProvider) ToFormat(topic *Topic) *format.Topic {
return topic.ToFormat()
}
func (o *TopicProvider) FromFormat(m *format.Topic) *Topic {
var topic Topic
topic.FromFormat(m)
return &topic
}
func (o *TopicProvider) GetObjects(user *User, project *Project, page int) []*Topic {
topics, _, err := repo_model.FindTopics(&repo_model.FindTopicOptions{
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
RepoID: project.GetID(),
})
if err != nil {
panic(err)
}
return util.ConvertMap[*repo_model.Topic, *Topic](topics, TopicConverter)
}
func (o *TopicProvider) ProcessObject(user *User, project *Project, topic *Topic) {
}
func (o *TopicProvider) Get(user *User, project *Project, exemplar *Topic) *Topic {
id := exemplar.GetID()
topic, err := repo_model.GetRepoTopicByID(o.g.ctx, project.GetID(), id)
if repo_model.IsErrTopicNotExist(err) {
return &Topic{}
}
if err != nil {
panic(err)
}
return TopicConverter(topic)
}
func (o *TopicProvider) Put(user *User, project *Project, topic *Topic) *Topic {
t, err := repo_model.AddTopic(project.GetID(), topic.Name)
if err != nil {
panic(err)
}
return o.Get(user, project, TopicConverter(t))
}
func (o *TopicProvider) Delete(user *User, project *Project, topic *Topic) *Topic {
t := o.Get(user, project, topic)
if !t.IsNil() {
t, err := repo_model.DeleteTopic(project.GetID(), t.Name)
if err != nil {
panic(err)
}
return TopicConverter(t)
}
return t
}

140
services/f3/driver/user.go Normal file
View File

@ -0,0 +1,140 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package driver
import (
"fmt"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/util"
user_service "code.gitea.io/gitea/services/user"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/common"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
f3_util "lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
type User struct {
user_model.User
}
func UserConverter(f *user_model.User) *User {
return &User{
User: *f,
}
}
func (o User) GetID() int64 {
return o.ID
}
func (o *User) SetID(id int64) {
o.ID = id
}
func (o *User) IsNil() bool {
return o.ID == 0
}
func (o *User) Equals(other *User) bool {
return (o.Name == other.Name)
}
func (o *User) ToFormat() *format.User {
return &format.User{
Common: format.Common{Index: o.ID},
UserName: o.Name,
Name: o.FullName,
Email: o.Email,
Password: o.Passwd,
}
}
func (o *User) FromFormat(user *format.User) {
*o = User{
User: user_model.User{
ID: user.Index,
Name: user.UserName,
FullName: user.Name,
Email: user.Email,
Passwd: user.Password,
},
}
}
type UserProvider struct {
g *Gitea
}
func (o *UserProvider) GetReferences(user *User) []common.Reference {
return nil
}
func (o *UserProvider) ToFormat(user *User) *format.User {
return user.ToFormat()
}
func (o *UserProvider) FromFormat(p *format.User) *User {
var user User
user.FromFormat(p)
return &user
}
func (o *UserProvider) GetObjects(page int) []*User {
users, _, err := user_model.SearchUsers(&user_model.SearchUserOptions{
Actor: o.g.GetDoer(),
Type: user_model.UserTypeIndividual,
ListOptions: db.ListOptions{Page: page, PageSize: o.g.perPage},
})
if err != nil {
panic(fmt.Errorf("error while listing users: %v", err))
}
return f3_util.ConvertMap[*user_model.User, *User](users, UserConverter)
}
func (o *UserProvider) ProcessObject(user *User) {
}
func (o *UserProvider) Get(exemplar *User) *User {
var user *user_model.User
var err error
if exemplar.GetID() > 0 {
user, err = user_model.GetUserByIDCtx(o.g.ctx, exemplar.GetID())
} else if exemplar.Name != "" {
user, err = user_model.GetUserByName(o.g.ctx, exemplar.Name)
} else {
panic("GetID() == 0 and UserName == \"\"")
}
if user_model.IsErrUserNotExist(err) {
return &User{}
}
if err != nil {
panic(fmt.Errorf("user %v %w", exemplar, err))
}
return UserConverter(user)
}
func (o *UserProvider) Put(user *User) *User {
overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: util.OptionalBoolTrue,
}
u := user.User
err := user_model.CreateUser(&u, overwriteDefault)
if err != nil {
panic(err)
}
return o.Get(UserConverter(&u))
}
func (o *UserProvider) Delete(user *User) *User {
u := o.Get(user)
if !u.IsNil() {
if err := user_service.DeleteUser(o.g.ctx, &user.User, true); err != nil {
panic(err)
}
}
return u
}

61
services/f3/util/util.go Normal file
View File

@ -0,0 +1,61 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package util
import (
"context"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/services/f3/driver"
"lab.forgefriends.org/friendlyforgeformat/gof3"
f3_forges "lab.forgefriends.org/friendlyforgeformat/gof3/forges"
"lab.forgefriends.org/friendlyforgeformat/gof3/forges/f3"
)
func ToF3Logger(messenger base.Messenger) gof3.Logger {
if messenger == nil {
messenger = func(string, ...interface{}) {}
}
return gof3.Logger{
Message: messenger,
Trace: log.Trace,
Debug: log.Debug,
Info: log.Info,
Warn: log.Warn,
Error: log.Error,
Critical: log.Critical,
Fatal: log.Fatal,
}
}
func GiteaForgeRoot(ctx context.Context, features gof3.Features, doer *user_model.User) *f3_forges.ForgeRoot {
forgeRoot := f3_forges.NewForgeRootFromDriver(&driver.Gitea{}, &driver.Options{
Options: gof3.Options{
Features: features,
Logger: ToF3Logger(nil),
},
Doer: doer,
})
forgeRoot.SetContext(ctx)
return forgeRoot
}
func F3ForgeRoot(ctx context.Context, features gof3.Features, directory string) *f3_forges.ForgeRoot {
forgeRoot := f3_forges.NewForgeRoot(&f3.Options{
Options: gof3.Options{
Configuration: gof3.Configuration{
Directory: directory,
},
Features: features,
Logger: ToF3Logger(nil),
},
Remap: true,
})
forgeRoot.SetContext(ctx)
return forgeRoot
}

View File

@ -0,0 +1,111 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package integration
import (
"bytes"
"context"
"flag"
"io"
"net/url"
"os"
"testing"
"code.gitea.io/gitea/cmd"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/migrations"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
f3_forges "lab.forgefriends.org/friendlyforgeformat/gof3/forges"
f3_util "lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
func Test_CmdF3(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
setting.Migrations.AllowLocalNetworks = true
// without migrations.Init() AllowLocalNetworks = true is not effective and
// a http call fails with "...migration can only call allowed HTTP servers..."
migrations.Init()
AppVer := setting.AppVer
// Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string.
setting.AppVer = "1.16.0"
defer func() {
setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
setting.AppVer = AppVer
}()
//
// Step 1: create a fixture
//
fixture := f3_forges.NewFixture(t, f3_forges.FixtureF3Factory)
fixture.NewUser()
fixture.NewMilestone()
fixture.NewLabel()
fixture.NewIssue()
fixture.NewTopic()
fixture.NewRepository()
fixture.NewRelease()
fixture.NewAsset()
fixture.NewIssueComment()
fixture.NewIssueReaction()
//
// Step 2: import the fixture into Gitea
//
cmd.CmdF3.Action = func(ctx *cli.Context) { cmd.RunF3(context.Background(), ctx) }
{
realStdout := os.Stdout // Backup Stdout
r, w, _ := os.Pipe()
os.Stdout = w
set := flag.NewFlagSet("f3", 0)
_ = set.Parse([]string{"f3", "--import", "--directory", fixture.ForgeRoot.GetDirectory()})
cliContext := cli.NewContext(&cli.App{Writer: os.Stdout}, set, nil)
assert.NoError(t, cmd.CmdF3.Run(cliContext))
w.Close()
var buf bytes.Buffer
io.Copy(&buf, r)
commandOutput := buf.String()
assert.EqualValues(t, "imported\n", commandOutput)
os.Stdout = realStdout
}
//
// Step 3: export Gitea into F3
//
directory := t.TempDir()
{
realStdout := os.Stdout // Backup Stdout
r, w, _ := os.Pipe()
os.Stdout = w
set := flag.NewFlagSet("f3", 0)
_ = set.Parse([]string{"f3", "--export", "--no-pull-request", "--user", fixture.UserFormat.UserName, "--repository", fixture.ProjectFormat.Name, "--directory", directory})
cliContext := cli.NewContext(&cli.App{Writer: os.Stdout}, set, nil)
assert.NoError(t, cmd.CmdF3.Run(cliContext))
w.Close()
var buf bytes.Buffer
io.Copy(&buf, r)
commandOutput := buf.String()
assert.EqualValues(t, "exported\n", commandOutput)
os.Stdout = realStdout
}
//
// Step 4: verify the export and import are equivalent
//
files := f3_util.Command(context.Background(), "find", directory)
assert.Contains(t, files, "/label/")
assert.Contains(t, files, "/issue/")
assert.Contains(t, files, "/milestone/")
assert.Contains(t, files, "/topic/")
assert.Contains(t, files, "/release/")
assert.Contains(t, files, "/asset/")
assert.Contains(t, files, "/reaction/")
})
}

View File

@ -0,0 +1,117 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package integration
import (
"context"
"net/url"
"testing"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/f3/util"
"github.com/stretchr/testify/assert"
"lab.forgefriends.org/friendlyforgeformat/gof3"
f3_forges "lab.forgefriends.org/friendlyforgeformat/gof3/forges"
f3_f3 "lab.forgefriends.org/friendlyforgeformat/gof3/forges/f3"
f3_gitea "lab.forgefriends.org/friendlyforgeformat/gof3/forges/gitea"
"lab.forgefriends.org/friendlyforgeformat/gof3/format"
f3_util "lab.forgefriends.org/friendlyforgeformat/gof3/util"
)
func TestF3(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
setting.Migrations.AllowLocalNetworks = true
AppVer := setting.AppVer
// Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string.
setting.AppVer = "1.16.0"
defer func() {
setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
setting.AppVer = AppVer
}()
//
// Step 1: create a fixture
//
fixtureNewF3Forge := func(t *testing.T, user *format.User) *f3_forges.ForgeRoot {
root := f3_forges.NewForgeRoot(&f3_f3.Options{
Options: gof3.Options{
Configuration: gof3.Configuration{
Directory: t.TempDir(),
},
Features: gof3.AllFeatures,
},
Remap: true,
})
root.SetContext(context.Background())
return root
}
fixture := f3_forges.NewFixture(t, f3_forges.FixtureForgeFactory{Fun: fixtureNewF3Forge, RootRequired: false})
fixture.NewUser()
fixture.NewMilestone()
fixture.NewLabel()
fixture.NewIssue()
fixture.NewTopic()
fixture.NewRepository()
fixture.NewPullRequest()
fixture.NewRelease()
fixture.NewAsset()
fixture.NewIssueComment()
fixture.NewPullRequestComment()
fixture.NewReview()
fixture.NewIssueReaction()
fixture.NewCommentReaction()
//
// Step 2: mirror the fixture into Gitea
//
doer, err := user_model.GetAdminUser()
assert.NoError(t, err)
giteaLocal := util.GiteaForgeRoot(context.Background(), gof3.AllFeatures, doer)
giteaLocal.Forge.Mirror(fixture.Forge)
//
// Step 3: mirror Gitea into F3
//
adminUsername := "user1"
giteaAPI := f3_forges.NewForgeRootFromDriver(&f3_gitea.Gitea{}, &f3_gitea.Options{
Options: gof3.Options{
Configuration: gof3.Configuration{
URL: setting.AppURL,
Directory: t.TempDir(),
},
Features: gof3.AllFeatures,
},
AuthToken: getUserToken(t, adminUsername),
})
giteaAPI.SetContext(context.Background())
f3 := f3_forges.FixtureNewF3Forge(t, nil)
apiForge := giteaAPI.Forge
apiUser := apiForge.Users.GetFromFormat(&format.User{UserName: fixture.UserFormat.UserName})
apiProject := apiUser.Projects.GetFromFormat(&format.Project{Name: fixture.ProjectFormat.Name})
f3.Forge.Mirror(apiForge, apiUser, apiProject)
//
// Step 4: verify the fixture and F3 are equivalent
//
files := f3_util.Command(context.Background(), "find", f3.GetDirectory())
assert.Contains(t, files, "/repository/git/hooks")
assert.Contains(t, files, "/label/")
assert.Contains(t, files, "/issue/")
assert.Contains(t, files, "/milestone/")
assert.Contains(t, files, "/topic/")
assert.Contains(t, files, "/pull_request/")
assert.Contains(t, files, "/release/")
assert.Contains(t, files, "/asset/")
assert.Contains(t, files, "/comment/")
assert.Contains(t, files, "/review/")
assert.Contains(t, files, "/reaction/")
// f3_util.Command(context.Background(), "cp", "-a", f3.GetDirectory(), "abc")
})
}