forked from mirrors/forgejo
Compare commits
49 commits
forgejo
...
bp-v13.0/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
621106123a | ||
|
|
f1a497d3c1 | ||
|
|
8ac5410a62 | ||
|
|
cb0845cd3e | ||
|
|
a50968d0de | ||
|
|
3bc1ae21ac | ||
|
|
b8448e7cde | ||
|
|
fc14793f7d |
||
|
|
fa1a2ba669 | ||
|
|
afbf1efe02 |
||
|
|
449b5bf10e |
||
|
|
8885844e72 |
||
|
|
a2068a47ce | ||
|
|
2c26525a9a | ||
|
|
6e26d31473 | ||
|
|
6e60d538bd | ||
|
|
2022feee7d | ||
|
|
494d5625e2 | ||
|
|
c7f2d7394b | ||
|
|
76afa21433 | ||
|
|
d7e08cfb8c | ||
|
|
714b88f8b2 | ||
|
|
d59c49ec52 | ||
|
|
48914b9465 | ||
|
|
f7603e7356 | ||
|
|
0fda75e08e | ||
|
|
08f37b5771 | ||
|
|
b1b99d5c70 | ||
|
|
5a622f7640 | ||
|
|
4dbd9c7261 | ||
|
|
9ff712ee6a | ||
|
|
01f0dbde9e | ||
|
|
83e7ff3ba2 | ||
|
|
72a38d19ac | ||
|
|
72faa337d0 | ||
|
|
65189bdb3e | ||
|
|
d6d4033a19 | ||
|
|
80fcbf165c | ||
|
|
2d3d5048d6 | ||
|
|
029a493779 | ||
|
|
877726a2bf | ||
|
|
6755ff1631 | ||
|
|
72abb7079b | ||
|
|
7a3fcbabf2 | ||
|
|
a674198198 | ||
|
|
d0b820039d | ||
|
|
d452207e50 | ||
|
|
010366d641 | ||
|
|
391f9a2f2c |
3113 changed files with 45244 additions and 143270 deletions
|
|
@ -11,7 +11,7 @@ include_file = ["main.go"]
|
|||
include_dir = ["cmd", "models", "modules", "options", "routers", "services"]
|
||||
exclude_dir = [
|
||||
"models/fixtures",
|
||||
"models/gitea_migrations/fixtures",
|
||||
"models/migrations/fixtures",
|
||||
"modules/avatar/identicon/testdata",
|
||||
"modules/avatar/testdata",
|
||||
"modules/git/tests",
|
||||
|
|
|
|||
|
|
@ -18,11 +18,9 @@ forgejo.org/models/auth
|
|||
|
||||
forgejo.org/models/db
|
||||
TruncateBeans
|
||||
TruncateBeansCascade
|
||||
InTransaction
|
||||
DumpTables
|
||||
GetTableNames
|
||||
extendBeansForCascade
|
||||
IsErrNameActivityPubInvalid
|
||||
|
||||
forgejo.org/models/dbfs
|
||||
file.renameTo
|
||||
|
|
@ -34,9 +32,6 @@ forgejo.org/models/forgejo/semver
|
|||
SetVersionString
|
||||
SetVersion
|
||||
|
||||
forgejo.org/models/forgejo_migrations
|
||||
resetMigrations
|
||||
|
||||
forgejo.org/models/git
|
||||
RemoveDeletedBranchByID
|
||||
|
||||
|
|
@ -50,14 +45,17 @@ forgejo.org/models/organization
|
|||
forgejo.org/models/perm/access
|
||||
GetRepoWriters
|
||||
|
||||
forgejo.org/models/repo
|
||||
WatchRepoMode
|
||||
|
||||
forgejo.org/models/user
|
||||
IsErrUserWrongType
|
||||
IsErrExternalLoginUserAlreadyExist
|
||||
IsErrExternalLoginUserNotExist
|
||||
NewFederatedUser
|
||||
IsErrUserSettingIsNotExist
|
||||
GetUserAllSettings
|
||||
DeleteUserSetting
|
||||
GetFederatedUser
|
||||
|
||||
forgejo.org/modules/activitypub
|
||||
NewContext
|
||||
|
|
@ -77,6 +75,7 @@ forgejo.org/modules/base
|
|||
SetupGiteaRoot
|
||||
|
||||
forgejo.org/modules/cache
|
||||
GetInt
|
||||
WithNoCacheContext
|
||||
RemoveContextData
|
||||
|
||||
|
|
@ -87,6 +86,7 @@ forgejo.org/modules/eventsource
|
|||
Event.String
|
||||
|
||||
forgejo.org/modules/forgefed
|
||||
NewForgeFollow
|
||||
NewForgeUndoLike
|
||||
ForgeUndoLike.UnmarshalJSON
|
||||
ForgeUndoLike.Validate
|
||||
|
|
@ -103,7 +103,6 @@ forgejo.org/modules/git
|
|||
AddChangesWithArgs
|
||||
CommitChanges
|
||||
CommitChangesWithArgs
|
||||
IsErrMoreThanOne
|
||||
SetUpdateHook
|
||||
openRepositoryWithDefaultContext
|
||||
ToEntryMode
|
||||
|
|
@ -132,9 +131,6 @@ forgejo.org/modules/json
|
|||
StdJSON.Indent
|
||||
|
||||
forgejo.org/modules/log
|
||||
eventWriterBuffer.Close
|
||||
eventWriterBuffer.Write
|
||||
eventWriterBuffer.GetString
|
||||
NewEventWriterBuffer
|
||||
|
||||
forgejo.org/modules/markup
|
||||
|
|
@ -225,22 +221,18 @@ forgejo.org/routers/web/org
|
|||
forgejo.org/services/context
|
||||
GetPrivateContext
|
||||
|
||||
forgejo.org/services/notify
|
||||
UnregisterNotifier
|
||||
forgejo.org/services/federation
|
||||
FollowRemoteActor
|
||||
|
||||
forgejo.org/services/repository
|
||||
IsErrForkAlreadyExist
|
||||
|
||||
forgejo.org/services/repository/files
|
||||
ContentType.String
|
||||
RepoFileOptionMode
|
||||
|
||||
forgejo.org/services/repository/gitgraph
|
||||
Parser.Reset
|
||||
|
||||
forgejo.org/services/stats
|
||||
Flush
|
||||
|
||||
forgejo.org/services/webhook
|
||||
NewNotifier
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"name": "forgejo-dev",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:1.26-trixie",
|
||||
"name": "Gitea DevContainer",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:1.24-bullseye",
|
||||
"features": {
|
||||
// installs nodejs into container
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "24"
|
||||
"version": "22"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git-lfs:1.2.5": {},
|
||||
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ insert_final_newline = false
|
|||
[options/locale/locale_*.ini]
|
||||
insert_final_newline = false
|
||||
|
||||
# Weblate is configured to use one tab for indention
|
||||
# Weblate JSON output defaults to four spaces
|
||||
[options/locale_next/locale_*.json]
|
||||
indent_style = tab
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
set -ex
|
||||
|
||||
# WARNING: Changes to the behaviour of this file should be backported to all active releases, as it is used in
|
||||
# `build-release.yml` from release branches.
|
||||
|
||||
end_to_end=$1
|
||||
end_to_end_pr=$2
|
||||
forgejo=$3
|
||||
|
|
|
|||
|
|
@ -1,107 +0,0 @@
|
|||
name: "Step 1: Report a problem or need"
|
||||
description: Please start here and describe your situation.
|
||||
title: "problem: "
|
||||
labels: ["problem", "impact/unknown"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**NOTE: If your issue is a security concern, please email <security@forgejo.org> ([security.txt](https://forgejo.org/.well-known/security.txt)) instead of opening a public issue.**
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
- Please speak English, as this is the language all maintainers can speak and write.
|
||||
- Be civil, and follow the [Forgejo Code of Conduct](https://codeberg.org/forgejo/code-of-conduct).
|
||||
- Take a moment to [check if a similar problem has already been discussed in the past.](https://codeberg.org/forgejo/forgejo/issues?q=&type=all&labels=78137). Feel free to add your own experience there, if applicable.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### New workflow
|
||||
|
||||
We are currently experimenting with a new workflow to manage issues and better understand your problems and needs. This is step 1 of the workflow: Please try to focus on explaining the problem you are facing, which could be a bug in the code, a moment of confusion, or a need that you have.
|
||||
|
||||
We do not expect anything from Forgejo users after creating a problem report, but we appreciate if you stay responsive to further questions. If you want, you can also participate in a discussion for solutions.
|
||||
|
||||
Forgejo contributors will review your report, try to understand how important it is to you and other Forgejo users, and suggest potential solutions. In a next step, solutions can be documented and implemented.
|
||||
|
||||
If you want to learn more about the background of our workflow, feel invited to read and participate in [the discussion that led to the current approach](https://codeberg.org/forgejo/discussions/issues/415). We are looking forward to your feedback.
|
||||
- type: dropdown
|
||||
id: can-reproduce
|
||||
attributes:
|
||||
label: Does your problem still exist on the latest Forgejo version?
|
||||
description: |
|
||||
Please try reproducing your problem at https://dev.next.forgejo.org or a local development version of Forgejo.
|
||||
If you check that your problem is not already addressed by a recent change, this will save all volunteers involved a lot of time.
|
||||
options:
|
||||
- "Yes, the problem still exists (tested on a next instance)"
|
||||
- "Yes, the problem still exists (tested locally with the latest development version)"
|
||||
- "Unknown, I can't try it for some reason (please explain)"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: About your usage of Forgejo
|
||||
description: |
|
||||
Please provide a brief description of your usage of Forgejo. There is no clear guideline on how much you need to share here. You can be brief, but we value the insights you provide us to better understand your use case. Thank you very much!
|
||||
<details><summary>Further instructions</summary>
|
||||
|
||||
* When reporting problems with certain functionality, you should include related information. Examples:
|
||||
* When reporting an issue with setting up an identity provider, it is useful to know if you use Forgejo in a 10-users non-profit / start up, or if you are talking about a school / university with several thousands of users.
|
||||
* When describing confusion, it will be relevant to know your skill level and background ("New, but used a similar product", "In my role as a project manager ..."), so we know for which target audience we need to design the functionality.
|
||||
* When reporting workflow issues or needs, it will be useful to know how large your project is, how many team members you have, which skill level we are talking about etc. For example, we might choose a different design depending on whether a feature is only for professional developers or for hobby coders.
|
||||
* If you want, we always appreciate generic information about your Forgejo usage to help us understand your usage and make better decisions. For example:
|
||||
* Your personal relation to Forgejo and user role ("I'm new to Forgejo, but used a comparable product called …", "In my role as a designer, …").
|
||||
* If you already explained your usage of Forgejo elsewhere (e.g. in another issue or in a user research repository), feel free to just drop a link.
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Problem description
|
||||
description: |
|
||||
Please describe your problem as a first-hand experience. Try to focus on the problem. You do not have to find a solution, you can leave this to us.
|
||||
<details><summary>Further instructions</summary>
|
||||
|
||||
* Start by explaining what you want to achieve ("I wanted to find an issue to work on").
|
||||
* Try to include steps that you took and that resulted in the current situation. ("I opened the issue tracker, clicked on …, then …").
|
||||
* If there were moments of confusion, please describe them. ("There was a button saying … and I was not sure if it would do what I expect").
|
||||
* If you want to do something and don't know how or if it is possible, explain the goal ("I would like to know if there are issues that meet the following criteria …").
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: workarounds
|
||||
attributes:
|
||||
label: Potential workarounds
|
||||
description: |
|
||||
If you found a solution to your problem, even if it is not great, please share your experiences with it.
|
||||
<details><summary>Further instructions</summary>
|
||||
|
||||
* Start by explaining what you tried and how it worked out ("I used X and it gives me the results, but it takes a lot of time to click through the UI").
|
||||
* What are the major problems with your workaround(s)? ("It takes long to get there", "It looks very ugly")
|
||||
|
||||
</details>
|
||||
- type: input
|
||||
id: forgejo-ver
|
||||
attributes:
|
||||
label: Forgejo Version
|
||||
description: Forgejo version (or commit reference) your instance is running or that you used to reproduce the bug on Forgejo Next.
|
||||
- type: textarea
|
||||
id: versions
|
||||
attributes:
|
||||
label: Other details about your environment (software names and versions)
|
||||
description: |
|
||||
Please include details to help us understand your problem: browser engine and version (for UI issues), operating system and version running Forgejo, database engine and version, deployment methods and relevant third-party packages (e.g. renderers, identity providers)
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### Solutions
|
||||
|
||||
*Accepted solutions to address this problem will go here*
|
||||
visible: [content]
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
name: "Step 2: Enhancement"
|
||||
description: "[Advanced users only] Suggest a solution to one or multiple problems that have already been reported (see step 1)."
|
||||
title: "enh: "
|
||||
labels: ["enhancement/feature"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
- Please speak English, as this is the language all maintainers can speak and write.
|
||||
- Be civil, and follow the [Forgejo Code of Conduct](https://codeberg.org/forgejo/code-of-conduct).
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### New workflow
|
||||
|
||||
We are currently experimenting with a new workflow to manage issues and better understand your problems and needs. This is step 2 of the workflow, which is intended for Forgejo contributors: If you just want to raise a problem or need you have, please refer to step 1.
|
||||
|
||||
This step allows to document a solution to one or multiple problems. It allows developers to focus on actionable implementation tasks without the clutter of previous discussions or triaging work.
|
||||
|
||||
If you want to learn more about the background of our workflow, feel invited to read and participate in [the discussion that led to the current approach](https://codeberg.org/forgejo/discussions/issues/415). We are looking forward to your feedback.
|
||||
- type: textarea
|
||||
id: problems
|
||||
attributes:
|
||||
label: Existing problems this enhancement addresses
|
||||
description: Only list the issue numbers of the **existing** problems that your proposal addresses. **Do not add new descriptions.** If you haven't previously described a problem, please [complete step one of the workflow](https://codeberg.org/forgejo/forgejo/issues/new?template=.forgejo%2fissue_template%2fproblem.yaml) and describe the problem you'd like to solve first.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Enhancement description
|
||||
description: Describe the changes you suggest for Forgejo and explain how they address the problems.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: details
|
||||
attributes:
|
||||
label: Details and notes
|
||||
description: Feel free to supply additional information like technical considerations, link to alternative solutions, UI mockups etc.
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
name: 🦋 Bug Report (web interface / frontend)
|
||||
description: "[Advanced users only] Something doesn't look quite as it should? Report it here!"
|
||||
description: Something doesn't look quite as it should? Report it here!
|
||||
title: "bug: "
|
||||
labels: ["bug/new-report", "forgejo/ui"]
|
||||
body:
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
name: 🐛 Bug Report (server / backend)
|
||||
description: "[Advanced users only] Found something you weren't expecting? Report it here!"
|
||||
description: Found something you weren't expecting? Report it here!
|
||||
title: "bug: "
|
||||
labels: bug/new-report
|
||||
body:
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🔓 Security Reports
|
||||
url: mailto:security@forgejo.org
|
||||
|
|
|
|||
31
.forgejo/issue_template/feature-request.yaml
Normal file
31
.forgejo/issue_template/feature-request.yaml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
name: 💡 Feature Request
|
||||
description: Got an idea for a feature that Forgejo doesn't have yet? Suggest it here!
|
||||
title: "feat: "
|
||||
labels: ["enhancement/feature"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
- Please speak English, as this is the language all maintainers can speak and write.
|
||||
- Be as clear and concise as possible. A very verbose request is harder to interpret in a concrete way.
|
||||
- Be civil, and follow the [Forgejo Code of Conduct](https://codeberg.org/forgejo/code-of-conduct).
|
||||
- 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](https://codeberg.org/forgejo/forgejo/issues?q=&type=all&labels=78139).
|
||||
- type: textarea
|
||||
id: needs-benefits
|
||||
attributes:
|
||||
label: Needs and benefits
|
||||
description: As concisely as possible, describe the benefits your feature request will provide or the problems it will try to solve.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Feature Description
|
||||
description: As concisely as possible, describe the feature you would like to see added or the changes you would like to see made to Forgejo.
|
||||
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.
|
||||
|
|
@ -10,22 +10,13 @@ labels:
|
|||
|
||||
## Checklist
|
||||
|
||||
The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).
|
||||
The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).
|
||||
|
||||
### Tests for Go changes
|
||||
|
||||
(can be removed for JavaScript changes)
|
||||
### Tests
|
||||
|
||||
- I added test coverage for Go changes...
|
||||
- [ ] in their respective `*_test.go` for unit tests.
|
||||
- [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
|
||||
- I ran...
|
||||
- [ ] `make pr-go` before pushing
|
||||
|
||||
### Tests for JavaScript changes
|
||||
|
||||
(can be removed for Go changes)
|
||||
|
||||
- I added test coverage for JavaScript changes...
|
||||
- [ ] in `web_src/js/*.test.js` if it can be unit tested.
|
||||
- [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).
|
||||
|
|
@ -37,9 +28,6 @@ The [contributor guide](https://forgejo.org/docs/next/contributor/) contains inf
|
|||
|
||||
### Release notes
|
||||
|
||||
- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
|
||||
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.
|
||||
|
||||
*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*
|
||||
|
||||
The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.
|
||||
- [ ] I do not want this change to show in the release notes.
|
||||
- [ ] I want the title to show in the release notes with a link to this pull request.
|
||||
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.
|
||||
|
|
|
|||
2
.forgejo/testdata/build-release/Dockerfile
vendored
2
.forgejo/testdata/build-release/Dockerfile
vendored
|
|
@ -1,4 +1,4 @@
|
|||
FROM data.forgejo.org/oci/alpine:3.23
|
||||
FROM data.forgejo.org/oci/alpine:3.22
|
||||
ARG RELEASE_VERSION=unkown
|
||||
LABEL maintainer="contact@forgejo.org" \
|
||||
org.opencontainers.image.version="${RELEASE_VERSION}"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ runs:
|
|||
steps:
|
||||
- run: |
|
||||
su forgejo -c 'make deps-backend'
|
||||
- uses: https://data.forgejo.org/actions/cache@v5
|
||||
- uses: https://data.forgejo.org/actions/cache@v4
|
||||
id: cache-backend
|
||||
with:
|
||||
path: ${{github.workspace}}/gitea
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ runs:
|
|||
apt-get update -qq
|
||||
apt-get -q install -y -qq curl ca-certificates
|
||||
|
||||
curl -sS -o /tmp/git-man.deb https://archive.ubuntu.com/ubuntu/pool/main/g/git/git-man_2.34.1-1ubuntu1_all.deb
|
||||
curl -sS -o /tmp/git-man.deb http://archive.ubuntu.com/ubuntu/pool/main/g/git/git-man_2.34.1-1ubuntu1_all.deb
|
||||
curl -sS -o /tmp/git.deb https://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1_amd64.deb
|
||||
curl -sS -o /tmp/git-lfs.deb https://archive.ubuntu.com/ubuntu/pool/universe/g/git-lfs/git-lfs_3.0.2-1_amd64.deb
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ runs:
|
|||
apt-get -q install -qq -y zstd
|
||||
|
||||
- name: "Set up Go using setup-go"
|
||||
uses: https://data.forgejo.org/actions/setup-go@v6
|
||||
uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
id: go-version
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
|
@ -50,11 +50,12 @@ runs:
|
|||
|
||||
- name: "Restore Go dependencies from cache or mark for later caching"
|
||||
id: cache-deps
|
||||
uses: https://data.forgejo.org/actions/cache@v5
|
||||
uses: https://data.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
key: setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-${{ steps.go-version.outputs.go_version }}-${{ hashFiles('go.sum', 'go.mod', 'Makefile') }}
|
||||
key: setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-${{ steps.go-version.outputs.go_version }}-${{ hashFiles('go.sum', 'go.mod') }}
|
||||
restore-keys: |
|
||||
setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-${{ steps.go-version.outputs.go_version }}-
|
||||
setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-
|
||||
path: |
|
||||
${{ steps.go-environment.outputs.modcache }}
|
||||
${{ steps.go-environment.outputs.cache }}
|
||||
|
|
|
|||
|
|
@ -40,14 +40,14 @@ jobs:
|
|||
)
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
steps:
|
||||
- name: event info
|
||||
run: |
|
||||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- uses: https://data.forgejo.org/actions/git-backporting@v4.9.1
|
||||
- uses: https://data.forgejo.org/actions/git-backporting@v4.8.5
|
||||
with:
|
||||
target-branch-pattern: "^backport/(?<target>(v.*))$"
|
||||
strategy: ort
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
name: Integration tests for the release process
|
||||
enable-email-notifications: true
|
||||
|
||||
env:
|
||||
FORGEJO_VERSION: 11.0.13 # renovate: datasource=docker depName=data.forgejo.org/forgejo/forgejo
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -29,14 +25,14 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-coding'
|
||||
runs-on: lxc-bookworm
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
|
||||
- id: forgejo
|
||||
uses: https://data.forgejo.org/actions/setup-forgejo@v3.1.11
|
||||
uses: https://data.forgejo.org/actions/setup-forgejo@v3.0.4
|
||||
with:
|
||||
user: root
|
||||
password: admin1234
|
||||
image-version: ${{ env.FORGEJO_VERSION }}
|
||||
image-version: 1.21
|
||||
lxc-ip-prefix: 10.0.9
|
||||
|
||||
- name: publish the forgejo release
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
# root is used for testing, allow it
|
||||
if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
@ -43,11 +43,11 @@ jobs:
|
|||
repository="${{ github.repository }}"
|
||||
echo "value=${repository##*/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-node@v6
|
||||
- uses: https://data.forgejo.org/actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v6
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ jobs:
|
|||
|
||||
- name: cache node_modules
|
||||
id: node
|
||||
uses: https://data.forgejo.org/actions/cache@v5
|
||||
uses: https://data.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
|
|
@ -164,7 +164,7 @@ jobs:
|
|||
|
||||
- name: build container & release
|
||||
if: ${{ secrets.TOKEN != '' }}
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.6.0
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.4.1
|
||||
with:
|
||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||
|
|
@ -183,7 +183,7 @@ jobs:
|
|||
|
||||
- name: build rootless container
|
||||
if: ${{ secrets.TOKEN != '' }}
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.6.0
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.4.1
|
||||
with:
|
||||
forgejo: "${{ env.GITHUB_SERVER_URL }}"
|
||||
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
|
||||
|
|
@ -206,7 +206,7 @@ jobs:
|
|||
origin-url: ${{ env.GITHUB_SERVER_URL }}
|
||||
origin-repo: ${{ github.repository }}
|
||||
origin-token: ${{ secrets.CASCADE_ORIGIN_TOKEN }}
|
||||
origin-ref: ${{ github.ref }}
|
||||
origin-ref: refs/heads/forgejo
|
||||
destination-url: https://code.forgejo.org
|
||||
destination-fork-repo: ${{ vars.CASCADE_DESTINATION_DOER }}/end-to-end
|
||||
destination-repo: forgejo/end-to-end
|
||||
|
|
@ -227,14 +227,11 @@ jobs:
|
|||
curl -sS -X DELETE $url/api/v1/repos/forgejo-experimental/forgejo/releases/tags/$tag > /dev/null
|
||||
curl -sS -X DELETE $url/api/v1/repos/forgejo-experimental/forgejo/tags/$tag > /dev/null
|
||||
fi
|
||||
# actions/checkout@v6 sets http.https://codeberg.org/.extraheader with the automatic token. Get rid of it so
|
||||
# it does not prevent using the token that has write permissions. As of @v6, it is stored in
|
||||
# $RUNNER_TEMP/git-credentials-(uuid).config and included in the repo via
|
||||
# includeif.gitdir:...=$RUNNER_TEMP/git-credentials-(uuid).config. Since we don't need these credentials
|
||||
# anymore we can just remove the generated config file.
|
||||
rm -f $RUNNER_TEMP/git-credentials-*
|
||||
# actions/checkout@v3 sets http.https://codeberg.org/.extraheader with the automatic token.
|
||||
# Get rid of it so it does not prevent using the token that has write permissions
|
||||
git config --local --unset http.https://codeberg.org/.extraheader
|
||||
if test -f .git/shallow ; then
|
||||
echo "unexpected .git/shallow file is present"
|
||||
echo "unexptected .git/shallow file is present"
|
||||
echo "it suggests a checkout --depth X was used which may prevent pushing the commit"
|
||||
echo "it happens when actions/checkout is called without depth: 0"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ jobs:
|
|||
)
|
||||
runs-on: docker
|
||||
container:
|
||||
image: data.forgejo.org/oci/node:24-bookworm
|
||||
image: data.forgejo.org/oci/node:22-bookworm
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
show-progress: 'false'
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000 --disable-log-bin
|
||||
options: --tmpfs /bitnami/mysql/data:noatime
|
||||
ldap:
|
||||
image: data.forgejo.org/oci/forgejo-test-openldap:latest
|
||||
image: data.forgejo.org/oci/test-openldap:latest
|
||||
pgsql:
|
||||
image: data.forgejo.org/oci/bitnami/postgresql:16
|
||||
env:
|
||||
|
|
@ -58,10 +58,10 @@ jobs:
|
|||
POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off
|
||||
options: --tmpfs /bitnami/postgresql
|
||||
cacher:
|
||||
image: registry.redict.io/redict:7.3.6-scratch
|
||||
image: registry.redict.io/redict:7.3.5-scratch
|
||||
options: --tmpfs /data:noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ inputs.repository }}
|
||||
ref: ${{ inputs.ref }}
|
||||
|
|
@ -83,7 +83,7 @@ jobs:
|
|||
TEST_MINIO_ENDPOINT: minio:9000
|
||||
TEST_LDAP: 1
|
||||
TEST_REDIS_SERVER: cacher:6379
|
||||
- uses: https://data.forgejo.org/forgejo/upload-artifact@v5
|
||||
- uses: https://code.forgejo.org/forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: coverage
|
||||
path: ${{ forge.workspace }}/coverage/merged
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-integration'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
steps:
|
||||
|
||||
- name: apt install curl jq
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
vars.ROLE == 'forgejo-coding' && forge.event.pull_request.head.repo.full_name != 'forgejo-cascading-pr/forgejo'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
steps:
|
||||
- name: Debug output
|
||||
run: |
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
if: ${{ secrets.MIRROR_TOKEN != '' }}
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-bookworm'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
steps:
|
||||
- name: git push {v*/,}forgejo
|
||||
run: |
|
||||
|
|
|
|||
|
|
@ -41,10 +41,10 @@ jobs:
|
|||
runs-on: lxc-bookworm
|
||||
if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != ''
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
|
||||
- name: copy & sign
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.6.0
|
||||
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.4.1
|
||||
with:
|
||||
from-forgejo: ${{ vars.FORGEJO }}
|
||||
to-forgejo: ${{ vars.FORGEJO }}
|
||||
|
|
@ -63,14 +63,14 @@ jobs:
|
|||
|
||||
- name: get trigger mirror issue
|
||||
id: mirror
|
||||
uses: https://data.forgejo.org/infrastructure/issue-action/get@v1.5.0
|
||||
uses: https://data.forgejo.org/infrastructure/issue-action/get@v1.3.0
|
||||
with:
|
||||
forgejo: https://code.forgejo.org
|
||||
repository: forgejo/forgejo
|
||||
labels: mirror-trigger
|
||||
|
||||
- name: trigger the mirror
|
||||
uses: https://data.forgejo.org/infrastructure/issue-action/set@v1.5.0
|
||||
uses: https://data.forgejo.org/infrastructure/issue-action/set@v1.3.0
|
||||
with:
|
||||
forgejo: https://code.forgejo.org
|
||||
repository: forgejo/forgejo
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ on:
|
|||
|
||||
env:
|
||||
RNA_WORKDIR: /srv/rna
|
||||
RNA_VERSION: v1.6.1 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
RNA_VERSION: v1.4.1 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
|
||||
jobs:
|
||||
release-notes:
|
||||
|
|
@ -15,9 +15,9 @@ jobs:
|
|||
container:
|
||||
image: 'data.forgejo.org/oci/ci:1'
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
|
||||
- uses: https://data.forgejo.org/actions/cache@v5
|
||||
- uses: https://data.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
key: rna-${{ env.RNA_VERSION }}
|
||||
path: ${{ env.RNA_WORKDIR }}
|
||||
|
|
|
|||
|
|
@ -8,16 +8,16 @@ on:
|
|||
- labeled
|
||||
|
||||
env:
|
||||
RNA_VERSION: v1.6.1 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
RNA_VERSION: v1.4.1 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
||||
|
||||
jobs:
|
||||
release-notes:
|
||||
if: ( vars.ROLE == 'forgejo-coding' ) && contains(github.event.pull_request.labels.*.name, 'worth a release-note')
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/ci:1'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
|
||||
- name: event
|
||||
run: |
|
||||
|
|
@ -28,12 +28,17 @@ jobs:
|
|||
${{ toJSON(github.event) }}
|
||||
EOF
|
||||
|
||||
- name: install release-notes-assistant
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache: false
|
||||
|
||||
- name: apt install jq
|
||||
run: |
|
||||
set -x
|
||||
wget -O /usr/local/bin/rna https://code.forgejo.org/forgejo/release-notes-assistant/releases/download/${{ env.RNA_VERSION}}/release-notes-assistant
|
||||
chmod +x /usr/local/bin/rna
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get -q install -y -qq jq
|
||||
|
||||
- name: release-notes-assistant preview
|
||||
run: |
|
||||
rna --config .release-notes-assistant.yaml --storage pr --storage-location ${{ github.event.pull_request.number }} --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} preview ${{ github.event.pull_request.number }}
|
||||
go run code.forgejo.org/forgejo/release-notes-assistant@$RNA_VERSION --config .release-notes-assistant.yaml --storage pr --storage-location ${{ github.event.pull_request.number }} --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} preview ${{ github.event.pull_request.number }}
|
||||
|
|
|
|||
78
.forgejo/workflows/renovate.yml
Normal file
78
.forgejo/workflows/renovate.yml
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#
|
||||
# Runs every 2 hours, but Renovate is limited to create new PR before 4am.
|
||||
# See renovate.json for more settings.
|
||||
# Automerge is enabled for Renovate PR's but need to be approved before.
|
||||
#
|
||||
name: renovate
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- renovate/** # self-test updates
|
||||
paths:
|
||||
- .forgejo/workflows/renovate.yml
|
||||
schedule:
|
||||
- cron: '0 0/2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
RENOVATE_DRY_RUN: ${{ (github.event_name != 'schedule' && github.ref_name != github.event.repository.default_branch) && 'full' || '' }}
|
||||
RENOVATE_REPOSITORIES: ${{ github.repository }}
|
||||
# fix because 10.0.0-58-7e1df53+gitea-1.22.0 < 10.0.0 for semver
|
||||
# and codeberg api returns such versions from `git describe --tags`
|
||||
# RENOVATE_X_PLATFORM_VERSION: 10.0.0+gitea-1.22.0 currently not needed
|
||||
|
||||
jobs:
|
||||
renovate:
|
||||
if: vars.ROLE == 'forgejo-coding' && secrets.RENOVATE_TOKEN != ''
|
||||
|
||||
runs-on: docker
|
||||
container:
|
||||
image: data.forgejo.org/renovate/renovate:41.122.3
|
||||
|
||||
steps:
|
||||
- name: Load renovate repo cache
|
||||
uses: https://data.forgejo.org/actions/cache/restore@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
with:
|
||||
path: |
|
||||
.tmp/cache/renovate/repository
|
||||
.tmp/cache/renovate/renovate-cache-sqlite
|
||||
.tmp/osv
|
||||
key: repo-cache-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
repo-cache-
|
||||
|
||||
- name: Run renovate
|
||||
run: renovate
|
||||
env:
|
||||
GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }}
|
||||
LOG_LEVEL: debug
|
||||
RENOVATE_BASE_DIR: ${{ github.workspace }}/.tmp
|
||||
RENOVATE_ENDPOINT: ${{ github.server_url }}
|
||||
RENOVATE_PLATFORM: forgejo
|
||||
RENOVATE_REPOSITORY_CACHE: 'enabled'
|
||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||
RENOVATE_GIT_AUTHOR: 'Renovate Bot <forgejo-renovate-action@forgejo.org>'
|
||||
|
||||
RENOVATE_X_SQLITE_PACKAGE_CACHE: true
|
||||
|
||||
GIT_AUTHOR_NAME: 'Renovate Bot'
|
||||
GIT_AUTHOR_EMAIL: 'forgejo-renovate-action@forgejo.org'
|
||||
GIT_COMMITTER_NAME: 'Renovate Bot'
|
||||
GIT_COMMITTER_EMAIL: 'forgejo-renovate-action@forgejo.org'
|
||||
|
||||
OSV_OFFLINE_ROOT_DIR: ${{ github.workspace }}/.tmp/osv
|
||||
|
||||
# use direct connection for these domains for renovate go datasource instead of the go proxy
|
||||
# allows faster lookups
|
||||
GONOPROXY: code.forgejo.org
|
||||
|
||||
- name: Save renovate repo cache
|
||||
if: always() && env.RENOVATE_DRY_RUN != 'full'
|
||||
uses: https://data.forgejo.org/actions/cache/save@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
with:
|
||||
path: |
|
||||
.tmp/cache/renovate/repository
|
||||
.tmp/cache/renovate/renovate-cache-sqlite
|
||||
.tmp/osv
|
||||
key: repo-cache-${{ github.run_id }}
|
||||
|
|
@ -31,10 +31,10 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-integration'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git 2.34.1 and git-lfs 3.0.2
|
||||
uses: ./.forgejo/workflows-composite/install-minimum-git-version
|
||||
|
|
@ -50,10 +50,10 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-integration'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git 2.34.1 and git-lfs 3.0.2
|
||||
uses: ./.forgejo/workflows-composite/install-minimum-git-version
|
||||
|
|
@ -75,7 +75,7 @@ jobs:
|
|||
matrix:
|
||||
version: ['10.6', '11.8']
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
mysql:
|
||||
|
|
@ -85,12 +85,12 @@ jobs:
|
|||
MARIADB_DATABASE: testgitea
|
||||
options: --tmpfs /var/lib/mysql:noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-mysql-migration test-mysql'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
name: testing
|
||||
enable-email-notifications: true
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
|
@ -14,7 +13,7 @@ jobs:
|
|||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- name: event info
|
||||
|
|
@ -22,31 +21,23 @@ jobs:
|
|||
cat <<'EOF'
|
||||
${{ toJSON(github) }}
|
||||
EOF
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
# DO NOT add checks here, but rather in the makefile
|
||||
- run: su forgejo -c './tools/cimake.sh pr-go'
|
||||
# this will re-run the backend target also contained in pr-go, but
|
||||
# a re-build is insignificant
|
||||
- run: su forgejo -c 'make deps-backend deps-tools'
|
||||
- run: su forgejo -c 'make --always-make -j$(nproc) lint-backend tidy-check swagger-check lint-swagger fmt-check swagger-validate' # ensure the "go-licenses" make target runs
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
frontend-checks:
|
||||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- run: make deps-frontend
|
||||
- run: make lint-frontend
|
||||
- run: make checks-frontend
|
||||
- name: make test-frontend-coverage
|
||||
run: |
|
||||
- run: |
|
||||
# Usage of `dayjs` can be impacted by local system timezone and can be sensitive to DST differences; since
|
||||
# frontend tests are very short they're run twice with varying DST rules to reduce regression risk.
|
||||
TZ=Europe/Berlin make test-frontend-coverage
|
||||
|
|
@ -58,8 +49,8 @@ jobs:
|
|||
run: |
|
||||
apt-get update -qq
|
||||
apt-get -q install -qq -y zstd
|
||||
- name: 'Cache frontend build for playwright testing'
|
||||
uses: https://data.forgejo.org/actions/cache/save@v5
|
||||
- name: "Cache frontend build for playwright testing"
|
||||
uses: https://data.forgejo.org/actions/cache/save@v4
|
||||
with:
|
||||
path: ${{github.workspace}}/public/assets
|
||||
key: frontend-build-${{ github.sha }}
|
||||
|
|
@ -68,7 +59,7 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
elasticsearch:
|
||||
|
|
@ -76,7 +67,7 @@ jobs:
|
|||
options: --tmpfs /bitnami/elasticsearch/data
|
||||
env:
|
||||
discovery.type: single-node
|
||||
ES_JAVA_OPTS: '-Xms512m -Xmx512m'
|
||||
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
|
||||
minio:
|
||||
image: data.forgejo.org/oci/bitnami/minio:2024.8.17
|
||||
options: >-
|
||||
|
|
@ -86,8 +77,12 @@ jobs:
|
|||
MINIO_ROOT_USER: 123456
|
||||
MINIO_ROOT_PASSWORD: 12345678
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git
|
||||
- name: test release-notes-assistant.sh
|
||||
run: |
|
||||
apt-get -q install -qq -y jq
|
||||
|
|
@ -109,17 +104,17 @@ jobs:
|
|||
image: 'data.forgejo.org/oci/playwright:latest'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 20
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: 'Restore frontend build'
|
||||
uses: https://data.forgejo.org/actions/cache/restore@v5
|
||||
- name: "Restore frontend build"
|
||||
uses: https://data.forgejo.org/actions/cache/restore@v4
|
||||
id: cache-frontend
|
||||
with:
|
||||
path: ${{github.workspace}}/public/assets
|
||||
key: frontend-build-${{ github.sha }}
|
||||
- name: 'Build frontend (if not cached)'
|
||||
- name: "Build frontend (if not cached)"
|
||||
if: steps.cache-frontend.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
su forgejo -c 'make deps-frontend frontend'
|
||||
|
|
@ -131,7 +126,7 @@ jobs:
|
|||
echo "all=1" >> "$GITHUB_OUTPUT"
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: https://data.forgejo.org/tj-actions/changed-files@v47
|
||||
uses: https://data.forgejo.org/tj-actions/changed-files@v46
|
||||
with:
|
||||
separator: '\n'
|
||||
- run: |
|
||||
|
|
@ -144,7 +139,7 @@ jobs:
|
|||
RUN_ALL: ${{steps.run-all.all}}
|
||||
- name: Upload test artifacts on failure
|
||||
if: failure()
|
||||
uses: https://data.forgejo.org/forgejo/upload-artifact@v5
|
||||
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: test-artifacts.zip
|
||||
path: tests/e2e/test-artifacts/
|
||||
|
|
@ -154,7 +149,7 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks, test-unit]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
name: ${{ format('test-remote-cacher ({0})', matrix.cacher.name) }}
|
||||
strategy:
|
||||
|
|
@ -176,11 +171,13 @@ jobs:
|
|||
cacher:
|
||||
image: ${{ matrix.cacher.image }}
|
||||
options: ${{ matrix.cacher.options }}
|
||||
env:
|
||||
ALLOW_EMPTY_PASSWORD: 'yes' # redis & valkey will immediately shutdown with no defined password unless overridden
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-remote-cacher test-check'
|
||||
|
|
@ -188,13 +185,13 @@ jobs:
|
|||
env:
|
||||
RACE_ENABLED: 'true'
|
||||
TAGS: bindata
|
||||
TEST_REDIS_SERVER: cacher:6379
|
||||
TEST_REDIS_SERVER: cacher:${{ matrix.cacher.port }}
|
||||
test-mysql:
|
||||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
mysql:
|
||||
|
|
@ -208,12 +205,12 @@ jobs:
|
|||
MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000 --disable-log-bin
|
||||
options: --tmpfs /bitnami/mysql/data:noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-mysql-migration test-mysql'
|
||||
|
|
@ -225,7 +222,7 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
services:
|
||||
minio:
|
||||
|
|
@ -246,12 +243,12 @@ jobs:
|
|||
POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off
|
||||
options: --tmpfs /bitnami/postgresql
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-pgsql-migration test-pgsql'
|
||||
|
|
@ -265,15 +262,15 @@ jobs:
|
|||
runs-on: docker
|
||||
needs: [backend-checks, frontend-checks]
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- name: install dependencies
|
||||
run: apt-get update -qq && apt-get -q install -qq -y git-lfs
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- name: install dependencies & git >= 2.42
|
||||
uses: ./.forgejo/workflows-composite/apt-install-from
|
||||
with:
|
||||
packages: git git-lfs
|
||||
- uses: ./.forgejo/workflows-composite/build-backend
|
||||
- run: |
|
||||
su forgejo -c 'make test-sqlite-migration test-sqlite'
|
||||
|
|
@ -293,23 +290,10 @@ jobs:
|
|||
- test-remote-cacher
|
||||
- test-unit
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/node:24-trixie'
|
||||
image: 'data.forgejo.org/oci/node:22-bookworm'
|
||||
options: --tmpfs /tmp:exec,noatime
|
||||
steps:
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||
- uses: ./.forgejo/workflows-composite/setup-env
|
||||
- run: su forgejo -c 'make deps-backend deps-tools'
|
||||
- run: su forgejo -c 'make security-check'
|
||||
semgrep:
|
||||
if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing'
|
||||
name: semgrep/ci
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'data.forgejo.org/oci/semgrep:latest'
|
||||
steps:
|
||||
- run: apk add nodejs # required for actions/checkout
|
||||
- uses: https://data.forgejo.org/actions/checkout@v6
|
||||
- name: self-check semgrep rules
|
||||
run: semgrep --test .semgrep/tests/ --config .semgrep/config/
|
||||
- name: semgrep ci
|
||||
run: semgrep ci --config .semgrep/config/ --metrics=off
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -107,7 +107,6 @@ cpu.out
|
|||
/.air
|
||||
/.go-licenses
|
||||
/.cur-deadcode-out
|
||||
/.deadcode.diff
|
||||
|
||||
# Files and folders that were previously generated
|
||||
/public/assets/img/webpack
|
||||
|
|
|
|||
260
.golangci.yml
260
.golangci.yml
|
|
@ -1,4 +1,3 @@
|
|||
---
|
||||
version: "2"
|
||||
output:
|
||||
sort-order:
|
||||
|
|
@ -13,9 +12,7 @@ linters:
|
|||
- forbidigo
|
||||
- gocritic
|
||||
- govet
|
||||
- importas
|
||||
- ineffassign
|
||||
- modernize
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- revive
|
||||
|
|
@ -26,7 +23,6 @@ linters:
|
|||
- unused
|
||||
- usetesting
|
||||
- wastedassign
|
||||
- nilnil
|
||||
settings:
|
||||
depguard:
|
||||
rules:
|
||||
|
|
@ -34,6 +30,10 @@ linters:
|
|||
deny:
|
||||
- pkg: encoding/json
|
||||
desc: use gitea's modules/json instead of encoding/json
|
||||
- pkg: github.com/unknwon/com
|
||||
desc: use gitea's util and replacements
|
||||
- pkg: io/ioutil
|
||||
desc: use os or io instead
|
||||
- pkg: golang.org/x/exp
|
||||
desc: it's experimental and unreliable
|
||||
- pkg: forgejo.org/modules/git/internal
|
||||
|
|
@ -46,47 +46,9 @@ linters:
|
|||
desc: use forgejo.org/modules/git instead, see https://codeberg.org/forgejo/forgejo/pulls/4941
|
||||
- pkg: gopkg.in/yaml.v3
|
||||
desc: use go.yaml.in/yaml instead, see https://codeberg.org/forgejo/forgejo/pulls/8956
|
||||
migration-isolation:
|
||||
list-mode: lax
|
||||
files:
|
||||
- "**/models/forgejo_migrations/**"
|
||||
deny:
|
||||
- pkg: "forgejo.org/models"
|
||||
desc: >
|
||||
Migrations must not import application models. Application models will be the most recent schema for
|
||||
Forgejo, while migrations will be operating against the database schema that existed when they were
|
||||
authored.
|
||||
- pkg: "forgejo.org/services"
|
||||
desc: >
|
||||
Migrations must not import application services. Application services will reference application
|
||||
models which will use the most recent schema for Forgejo, while migrations will be operating against the
|
||||
database schema that existed when they were authored.
|
||||
allow:
|
||||
- "forgejo.org/models/db"
|
||||
- "forgejo.org/models/gitea_migrations/base"
|
||||
- "forgejo.org/models/gitea_migrations/test"
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
importas:
|
||||
alias:
|
||||
# Specific overrides that would violate the default rules further below.
|
||||
- pkg: forgejo.org/models/db
|
||||
alias: ""
|
||||
- pkg: forgejo.org/models/organization
|
||||
alias: org_model
|
||||
|
||||
- pkg: forgejo.org/services/context
|
||||
alias: app_context
|
||||
- pkg: forgejo.org/services/doctor
|
||||
alias: doctor
|
||||
- pkg: forgejo.org/services/repository
|
||||
alias: repo_service
|
||||
|
||||
# Make sure that we follow a consistent naming for model and service aliases.
|
||||
- pkg: 'forgejo.org/(model|service)s/(\w+)'
|
||||
alias: '${2}_${1}'
|
||||
|
||||
revive:
|
||||
severity: error
|
||||
rules:
|
||||
|
|
@ -132,10 +94,7 @@ linters:
|
|||
- all
|
||||
testifylint:
|
||||
disable:
|
||||
- error-is-as
|
||||
- go-require
|
||||
nilnil:
|
||||
only-two: false
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
|
|
@ -160,7 +119,7 @@ linters:
|
|||
- errcheck
|
||||
- gocyclo
|
||||
- gosec
|
||||
path: models/gitea_migrations/v
|
||||
path: models/migrations/v
|
||||
- linters:
|
||||
- forbidigo
|
||||
path: cmd
|
||||
|
|
@ -186,215 +145,6 @@ linters:
|
|||
- linters:
|
||||
- staticcheck
|
||||
text: "(ST1005|ST1003|QF1001):"
|
||||
|
||||
# TODO: eventually remove this section entirely
|
||||
- path: cmd/admin_auth_ldap_test.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: cmd/admin_auth_oauth_test.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: cmd/admin_auth_pam_test.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: cmd/cmd.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: cmd/forgejo/actions.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/actions/run.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/actions/task.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/activities/action_list.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/asymkey/gpg_key_object_verification.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/auth/oauth2.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/db/collation.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/dbfs/dbfile.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/forgejo_migrations_legacy/v32.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/forgejo_migrations_legacy/v32_test.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/db/context.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/git/branch_list.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/git/lfs_lock.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/git/protected_branch.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/git/protected_tag.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/issues/issue.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/issues/issue_xref.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/issues/review.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/organization/org_user.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/quota/rule.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/repo/archiver.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/repo/fork.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/repo/topic.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/user/email_address.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/user/list.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/user/user.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: models/repo/repo.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: modules/git/commit.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: modules/git/foreachref/parser.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: modules/git/last_commit_cache.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: modules/git/log_name_status.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: modules/graceful/net_unix.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: modules/indexer/internal/bleve/util.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: modules/indexer/issues/util.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: modules/optional/serialization.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: modules/setting/storage.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: routers/api/packages/chef/auth.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: routers/api/packages/container/auth.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: routers/api/packages/nuget/auth.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: routers/api/packages/swift/swift.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: routers/web/auth/oauth.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: routers/web/repo/compare.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: routers/web/repo/release.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: routers/web/repo/setting/runners.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: routers/web/repo/setting/secrets.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: routers/web/repo/setting/variables.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/actions/context.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/actions/task.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/actions/trust.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/contexttest/context_tests.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/gitdiff/csv.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/issue/assignee.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: routers/api/packages/conan/auth.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/issue/commit.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/issue/issue.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/migrations/onedev.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/packages/cargo/index.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/pull/check.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/pull/comment.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/pull/merge.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/pull/review.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/remote/promote.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/repository/archiver/archiver.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/repository/generate_repo_commit.go
|
||||
linters:
|
||||
- nilnil
|
||||
- path: services/repository/repository.go
|
||||
linters:
|
||||
- nilnil
|
||||
paths:
|
||||
- node_modules
|
||||
- public
|
||||
|
|
|
|||
17
.mockery.yml
17
.mockery.yml
|
|
@ -1,17 +0,0 @@
|
|||
formatter: gofmt
|
||||
template: testify
|
||||
packages:
|
||||
forgejo.org/modules/nosql:
|
||||
config:
|
||||
filename: mocks.go # make mocks public so that external packages can use
|
||||
forgejo.org/services/auth/method:
|
||||
forgejo.org/services/authz:
|
||||
config:
|
||||
filename: authorization_reducer_mock.go # make mocks public so that external packages can use
|
||||
code.forgejo.org/go-chi/cache:
|
||||
interfaces:
|
||||
Cache:
|
||||
config:
|
||||
pkgname: cache
|
||||
dir: modules/cache
|
||||
filename: mocks.go # make mocks public, not `_test.go`, so that external packages can mock caching
|
||||
|
|
@ -1 +0,0 @@
|
|||
24.15.0
|
||||
|
|
@ -5,10 +5,8 @@ branch-find-version: 'v(?P<version>\d+\.\d+)/forgejo'
|
|||
branch-to-version: '${version}.0'
|
||||
branch-from-version: 'v%[1]d.%[2]d/forgejo'
|
||||
tag-from-version: 'v%[1]d.%[2]d.%[3]d'
|
||||
supported-release-count: 3
|
||||
branch-known:
|
||||
# replace with v15 when v11 becomes EOL
|
||||
- 'v11.0/forgejo'
|
||||
- 'v7.0/forgejo'
|
||||
cleanup-line: 'sed -Ee "s/^(feat|fix):\s*//g" -e "s/^\[WIP\] //" -e "s/^WIP: //" -e "s;\[(UI|BUG|FEAT|v.*?/forgejo)\]\s*;;g"'
|
||||
render-header: |
|
||||
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
rules:
|
||||
- id: forgejo-api-use-resource-SearchRepoOptions
|
||||
patterns:
|
||||
- pattern: |
|
||||
repo_model.SearchRepoOptions{...}
|
||||
- pattern-not: |
|
||||
repo_model.SearchRepoOptions{
|
||||
...,
|
||||
AuthorizationReducer: ctx.Reducer,
|
||||
...
|
||||
}
|
||||
languages:
|
||||
- go
|
||||
message: >
|
||||
SearchRepoOptions does not take into account fine-grained access token limitations. Include the
|
||||
AuthorizationReducer field.
|
||||
severity: ERROR
|
||||
paths:
|
||||
include:
|
||||
- "/routers/api/**/*.go"
|
||||
|
||||
- id: forgejo-api-use-resource-SearchRepoOptions
|
||||
patterns:
|
||||
- pattern: |
|
||||
organization.SearchTeamRepoOptions{...}
|
||||
- pattern-not: |
|
||||
organization.SearchTeamRepoOptions{
|
||||
...,
|
||||
AuthorizationReducer: ctx.Reducer,
|
||||
...
|
||||
}
|
||||
languages:
|
||||
- go
|
||||
message: >
|
||||
SearchTeamRepoOptions does not take into account fine-grained access token limitations. Include the
|
||||
AuthorizationReducer field.
|
||||
severity: ERROR
|
||||
paths:
|
||||
include:
|
||||
- "/routers/api/**/*.go"
|
||||
|
||||
- id: forgejo-api-use-resource-GetUserRepoPermission
|
||||
patterns:
|
||||
- pattern: |
|
||||
$X.GetUserRepoPermission($CTX, $REPO, $DOER)
|
||||
- metavariable-type:
|
||||
metavariable: $CTX
|
||||
types:
|
||||
- "*context.APIContext"
|
||||
languages:
|
||||
- go
|
||||
message: >
|
||||
GetUserRepoPermission does not take into account fine-grained access token limitations. Use
|
||||
GetUserRepoPermissionWithReducer.
|
||||
fix: |
|
||||
$X.GetUserRepoPermissionWithReducer($CTX, $REPO, $DOER, $CTX.Reducer)
|
||||
severity: ERROR
|
||||
|
||||
- id: forgejo-api-suspicious-GetUserRepoPermission
|
||||
patterns:
|
||||
- pattern: $X.GetUserRepoPermission($CTX, $REPO, $DOER)
|
||||
- pattern-not: # don't match if identical to forgejo-api-use-resource-GetUserRepoPermission
|
||||
patterns:
|
||||
- pattern: |
|
||||
$X.GetUserRepoPermission($CTX, $REPO, $DOER)
|
||||
- metavariable-type:
|
||||
metavariable: $CTX
|
||||
types:
|
||||
- "*context.APIContext"
|
||||
languages:
|
||||
- go
|
||||
message: >
|
||||
API code is accessing GetUserRepoPermission which does not take into account fine-grained access token
|
||||
limitations. Should this use GetUserRepoPermissionWithReducer?
|
||||
severity: ERROR
|
||||
paths:
|
||||
include:
|
||||
- "/routers/api/**/*.go"
|
||||
|
||||
- id: forgejo-api-direct-IsAdmin-check
|
||||
patterns:
|
||||
- pattern: |
|
||||
ctx.Doer.IsAdmin
|
||||
languages:
|
||||
- go
|
||||
message: |
|
||||
ctx.Doer.IsAdmin does not take into account limited API access tokens. Use ctx.IsUserSiteAdmin() instead.
|
||||
fix: |
|
||||
ctx.IsUserSiteAdmin()
|
||||
severity: ERROR
|
||||
paths:
|
||||
include:
|
||||
- "/routers/api/**/*.go"
|
||||
|
||||
- id: forgejo-api-direct-repo-Admin-check
|
||||
patterns:
|
||||
- pattern: |
|
||||
ctx.Repo.IsAdmin()
|
||||
- pattern: |
|
||||
ctx.Repo.IsOwner()
|
||||
languages:
|
||||
- go
|
||||
message: |
|
||||
ctx.Repo.IsAdmin/IsOwner() does not take into account limited API access tokens. Use ctx.IsUserRepoAdmin() instead.
|
||||
fix: |
|
||||
ctx.IsUserRepoAdmin()
|
||||
severity: ERROR
|
||||
paths:
|
||||
include:
|
||||
- "/routers/api/**/*.go"
|
||||
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
rules:
|
||||
- id: forgejo-switch-empty-case
|
||||
pattern-either:
|
||||
- pattern: |-
|
||||
switch $_ {
|
||||
case $_:
|
||||
}
|
||||
- patterns:
|
||||
- pattern: |-
|
||||
switch {
|
||||
case $_:
|
||||
}
|
||||
languages:
|
||||
- go
|
||||
severity: ERROR
|
||||
message: >
|
||||
switch has a case block with no content. This is treated as "break" by Go, but developers may confuse it for
|
||||
"fallthrough". To fix this error, disambiguate by using "break" or "fallthrough".
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
rules:
|
||||
- id: forgejo-logic-suspicious-OwnerID-check
|
||||
pattern: |-
|
||||
$X.OwnerID > 0
|
||||
languages:
|
||||
- go
|
||||
severity: ERROR
|
||||
message: >
|
||||
Many resources like comments or runners cannot only be owned by regular users, which have positive IDs, but also
|
||||
by predefined system users like Ghost or Forgejo Actions that have negative IDs. In those cases, ownership checks
|
||||
should only exclude 0: `OwnerID != 0`.
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
rules:
|
||||
- id: xorm-sync-missing-ignore-drop-indices
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- pattern: |
|
||||
$X.Sync(...)
|
||||
- pattern: |
|
||||
$X.SyncWithOptions($OPTS, ...)
|
||||
- pattern-not: |
|
||||
$X.SyncWithOptions(xorm.SyncOptions{..., IgnoreDropIndices: true, ...}, ...)
|
||||
- metavariable-type:
|
||||
metavariable: $X
|
||||
types:
|
||||
- "*xorm.Engine"
|
||||
- "*xorm.Session"
|
||||
paths:
|
||||
exclude:
|
||||
- /models/gitea_migrations/**/*.go
|
||||
- /models/forgejo_migrations_legacy/**/*.go
|
||||
languages:
|
||||
- go
|
||||
message: |
|
||||
xorm Sync operation may drop indices if used on an incomplete bean definition for an existing table. Use SyncWithOptions with IgnoreDropIndices: true instead.
|
||||
severity: ERROR
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
access_model "forgejo.org/models/perm/access"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
api "forgejo.org/modules/structs"
|
||||
"forgejo.org/routers/api/v1/utils"
|
||||
"forgejo.org/services/context"
|
||||
"forgejo.org/services/convert"
|
||||
)
|
||||
|
||||
// ListForks list a repository's forks
|
||||
func ListForks(ctx *context.APIContext) {
|
||||
forks, total, err := repo_model.GetForks(ctx, ctx.Repo.Repository, ctx.Doer, utils.GetListOptions(ctx))
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetForks", err)
|
||||
return
|
||||
}
|
||||
apiForks := make([]*api.Repository, len(forks))
|
||||
for i, fork := range forks {
|
||||
// ruleid:forgejo-api-use-resource-GetUserRepoPermission
|
||||
permission, err := access_model.GetUserRepoPermissionWithReducer(ctx, fork, ctx.Doer, ctx.Reducer)
|
||||
// ok:forgejo-api-use-resource-GetUserRepoPermission
|
||||
permission, err := access_model.GetUserRepoPermissionWithReducer(ctx, fork, ctx.Doer, ctx.Reducer)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
apiForks[i] = convert.ToRepo(ctx, fork, permission)
|
||||
}
|
||||
}
|
||||
|
||||
// getStarredRepos returns the repos that the user with the specified userID has
|
||||
// starred
|
||||
func getStarredRepos(ctx std_context.Context, user *user_model.User, private bool, listOptions db.ListOptions) ([]*api.Repository, error) {
|
||||
starredRepos, err := repo_model.GetStarredRepos(ctx, user.ID, private, listOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repos := make([]*api.Repository, len(starredRepos))
|
||||
for i, starred := range starredRepos {
|
||||
// ruleid:forgejo-api-suspicious-GetUserRepoPermission
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, starred, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, starred, permission)
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
access_model "forgejo.org/models/perm/access"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
api "forgejo.org/modules/structs"
|
||||
"forgejo.org/routers/api/v1/utils"
|
||||
"forgejo.org/services/context"
|
||||
"forgejo.org/services/convert"
|
||||
)
|
||||
|
||||
// ListForks list a repository's forks
|
||||
func ListForks(ctx *context.APIContext) {
|
||||
forks, total, err := repo_model.GetForks(ctx, ctx.Repo.Repository, ctx.Doer, utils.GetListOptions(ctx))
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetForks", err)
|
||||
return
|
||||
}
|
||||
apiForks := make([]*api.Repository, len(forks))
|
||||
for i, fork := range forks {
|
||||
// ruleid:forgejo-api-use-resource-GetUserRepoPermission
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, fork, ctx.Doer)
|
||||
// ok:forgejo-api-use-resource-GetUserRepoPermission
|
||||
permission, err := access_model.GetUserRepoPermissionWithReducer(ctx, fork, ctx.Doer, ctx.Reducer)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
apiForks[i] = convert.ToRepo(ctx, fork, permission)
|
||||
}
|
||||
}
|
||||
|
||||
// getStarredRepos returns the repos that the user with the specified userID has
|
||||
// starred
|
||||
func getStarredRepos(ctx std_context.Context, user *user_model.User, private bool, listOptions db.ListOptions) ([]*api.Repository, error) {
|
||||
starredRepos, err := repo_model.GetStarredRepos(ctx, user.ID, private, listOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repos := make([]*api.Repository, len(starredRepos))
|
||||
for i, starred := range starredRepos {
|
||||
// ruleid:forgejo-api-suspicious-GetUserRepoPermission
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, starred, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, starred, permission)
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
)
|
||||
|
||||
func setPortEmptyCaseBad(port string) error {
|
||||
setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, port, 1)
|
||||
setting.HTTPPort = port
|
||||
|
||||
// ruleid:forgejo-switch-empty-case
|
||||
switch setting.Protocol {
|
||||
case setting.HTTPUnix:
|
||||
case setting.FCGI:
|
||||
case setting.FCGIUnix:
|
||||
default:
|
||||
defaultLocalURL := string(setting.Protocol) + "://"
|
||||
}
|
||||
|
||||
// ok:forgejo-switch-empty-case
|
||||
switch setting.Protocol {
|
||||
case setting.HTTPUnix:
|
||||
break
|
||||
case setting.FCGI:
|
||||
break
|
||||
case setting.FCGIUnix:
|
||||
break
|
||||
default:
|
||||
defaultLocalURL := string(setting.Protocol) + "://"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import "xorm.io/builder"
|
||||
|
||||
type FindRunJobOptions struct {
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
}
|
||||
|
||||
func (opts FindRunJobOptions) Bad() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
// ruleid:forgejo-logic-suspicious-OwnerID-check
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func (opts FindRunJobOptions) Good() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
// ok:forgejo-logic-suspicious-OwnerID-check
|
||||
if opts.OwnerID != 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package forgejo_migrations
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type ActionUser struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"INDEX UNIQUE(action_user_index) REFERENCES(user, id)"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(action_user_index) REFERENCES(repository, id)"`
|
||||
|
||||
TrustedWithPullRequests bool
|
||||
|
||||
LastAccess timeutil.TimeStamp `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
func testSyncBad1(x *xorm.Engine) error {
|
||||
// ruleid:xorm-sync-missing-ignore-drop-indices
|
||||
return x.Sync(new(ActionUser))
|
||||
}
|
||||
|
||||
func testSyncBad2(x *xorm.Engine) error {
|
||||
// ruleid:xorm-sync-missing-ignore-drop-indices
|
||||
_, err = x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: false}, bean)
|
||||
return err
|
||||
}
|
||||
|
||||
func testSyncGood1(x *xorm.Engine) error {
|
||||
// ok:xorm-sync-missing-ignore-drop-indices
|
||||
_, err = x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, bean)
|
||||
return err
|
||||
}
|
||||
|
||||
func testSyncGood2(x *fs.File) error {
|
||||
// ok:xorm-sync-missing-ignore-drop-indices
|
||||
_, err = x.Sync()
|
||||
return err
|
||||
}
|
||||
21
CODEOWNERS
21
CODEOWNERS
|
|
@ -38,25 +38,6 @@ routers/.* @gusted
|
|||
options/locale/.* @0ko
|
||||
options/locale_next/.* @0ko
|
||||
|
||||
# lint-locale-usage
|
||||
build/lint-locale-usage/.* @fogti
|
||||
models/unit/.* @fogti
|
||||
services/migrations/lint-locale-usage/.* @fogti
|
||||
|
||||
# Personal interest
|
||||
build/lint-locale-usage/.* @fogti
|
||||
.*/webhook.* @oliverpool
|
||||
|
||||
# Often work with, and on, the API
|
||||
modules/structs/.* @Cyborus
|
||||
routers/api/v1/.* @Cyborus
|
||||
routers/api/forgejo/.* @Cyborus
|
||||
tests/integration/api_.* @Cyborus
|
||||
|
||||
# Federation code, requires care to be taken with regards to interoperability
|
||||
# and backwards compatibility due to the way signatures work.
|
||||
services/federation/.* @famfo @0xllx0
|
||||
modules/forgefed/.* @famfo @0xllx0
|
||||
models/forgefed/.* @famfo @0xllx0
|
||||
routers/api/v1/activitypub/.* @famfo @0xllx0
|
||||
tests/integration/api_activitypub_.* @famfo @0xllx0
|
||||
tests/integration/activitypub_.* @famfo @0xllx0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.26-alpine3.23 AS build-env
|
||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.25-alpine3.22 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct}
|
||||
|
|
@ -51,7 +51,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
|||
/go/src/forgejo.org/environment-to-ini
|
||||
RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete
|
||||
|
||||
FROM data.forgejo.org/oci/alpine:3.23
|
||||
FROM data.forgejo.org/oci/alpine:3.22
|
||||
ARG RELEASE_VERSION
|
||||
LABEL maintainer="contact@forgejo.org" \
|
||||
org.opencontainers.image.authors="Forgejo" \
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.26-alpine3.23 AS build-env
|
||||
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.25-alpine3.22 AS build-env
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct}
|
||||
|
|
@ -49,7 +49,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
|||
/go/src/forgejo.org/environment-to-ini
|
||||
RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete
|
||||
|
||||
FROM data.forgejo.org/oci/alpine:3.23
|
||||
FROM data.forgejo.org/oci/alpine:3.22
|
||||
ARG RELEASE_VERSION
|
||||
LABEL maintainer="contact@forgejo.org" \
|
||||
org.opencontainers.image.authors="Forgejo" \
|
||||
|
|
@ -86,8 +86,8 @@ RUN addgroup \
|
|||
-G git \
|
||||
git
|
||||
|
||||
RUN mkdir -p /var/lib/gitea
|
||||
RUN chown git:git /var/lib/gitea
|
||||
RUN mkdir -p /var/lib/gitea /etc/gitea
|
||||
RUN chown git:git /var/lib/gitea /etc/gitea
|
||||
|
||||
COPY --from=build-env /tmp/local /
|
||||
RUN cd /usr/local/bin ; ln -s gitea forgejo
|
||||
|
|
@ -103,9 +103,13 @@ ENV GITEA_CUSTOM=/var/lib/gitea/custom
|
|||
ENV GITEA_TEMP=/tmp/gitea
|
||||
ENV TMPDIR=/tmp/gitea
|
||||
|
||||
# Legacy config file for backwards compatibility
|
||||
# TODO: remove on next major version release
|
||||
ENV GITEA_APP_INI_LEGACY=/etc/gitea/app.ini
|
||||
|
||||
ENV GITEA_APP_INI=${GITEA_CUSTOM}/conf/app.ini
|
||||
ENV HOME="/var/lib/gitea/git"
|
||||
VOLUME ["/var/lib/gitea"]
|
||||
VOLUME ["/var/lib/gitea", "/etc/gitea"]
|
||||
WORKDIR /var/lib/gitea
|
||||
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
||||
|
|
|
|||
84
Makefile
84
Makefile
|
|
@ -37,18 +37,17 @@ endif
|
|||
XGO_VERSION := go-1.21.x
|
||||
|
||||
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.6.1 # renovate: datasource=go
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 # renovate: datasource=go
|
||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.3.0 # renovate: datasource=go
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0 # renovate: datasource=go
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.3.1 # renovate: datasource=go
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.2 # renovate: datasource=go
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 # renovate: datasource=go
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses/v2@v2.0.1 # renovate: datasource=go
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.45.0 # renovate: datasource=go
|
||||
ERRORTYPE_PACKAGE ?= fillmore-labs.com/errortype@v0.0.11 # renovate: datasource=go
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@43.160.6 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
MOCKERY_PACKAGE ?= github.com/vektra/mockery/v3@v3.7.0 # renovate: datasource=go
|
||||
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.36.0 # renovate: datasource=go
|
||||
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.6.0 # renovate: datasource=go
|
||||
RENOVATE_NPM_PACKAGE ?= renovate@41.122.3 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
|
||||
|
||||
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
|
||||
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
|
||||
|
|
@ -245,7 +244,7 @@ help:
|
|||
@echo " - generate-license update license files"
|
||||
@echo " - generate-gitignore update gitignore files"
|
||||
@echo " - generate-manpage generate manpage"
|
||||
@echo " - generate-mockery generate mockery files"
|
||||
@echo " - generate-gomock generate gomock files"
|
||||
@echo " - generate-forgejo-api generate the forgejo API from spec"
|
||||
@echo " - forgejo-api-validate check if the forgejo API matches the specs"
|
||||
@echo " - generate-swagger generate the swagger spec from code comments"
|
||||
|
|
@ -288,14 +287,14 @@ show-version-api: verify-version
|
|||
.PHONY: compute-go-test-packages
|
||||
compute-go-test-packages:
|
||||
ifeq ($(HAS_GO), yes)
|
||||
$(eval GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list forgejo.org/models/gitea_migrations/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations_legacy/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations/...) forgejo.org/tests/integration/migration-test forgejo.org/tests forgejo.org/tests/integration forgejo.org/tests/e2e,$(shell $(GO) list ./...)))
|
||||
$(eval GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list forgejo.org/models/migrations/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations/...) forgejo.org/tests/integration/migration-test forgejo.org/tests forgejo.org/tests/integration forgejo.org/tests/e2e,$(shell $(GO) list ./...)))
|
||||
endif
|
||||
|
||||
# Target to compute MIGRATION_PACKAGES - only runs when needed
|
||||
.PHONY: compute-migration-packages
|
||||
compute-migration-packages:
|
||||
ifeq ($(HAS_GO), yes)
|
||||
$(eval MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/gitea_migrations/... forgejo.org/models/forgejo_migrations_legacy/... forgejo.org/models/forgejo_migrations/...))
|
||||
$(eval MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/migrations/... forgejo.org/models/forgejo_migrations/...))
|
||||
endif
|
||||
|
||||
###
|
||||
|
|
@ -323,7 +322,7 @@ git-check:
|
|||
node-check:
|
||||
$(eval MIN_NODE_VERSION_STR := $(shell grep -Eo '"node":.*[0-9.]+"' package.json | sed -n 's/.*[^0-9.]\([0-9.]*\)"/\1/p'))
|
||||
$(eval MIN_NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' ')))
|
||||
$(eval NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v | cut -c2- | sed 's:-.*::' | tr '.' ' ');))
|
||||
$(eval NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v | cut -c2- | tr '.' ' ');))
|
||||
$(eval NPM_MISSING := $(shell hash npm > /dev/null 2>&1 || echo 1))
|
||||
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" -o "$(NPM_MISSING)" = "1" ]; then \
|
||||
echo "Forgejo requires Node.js $(MIN_NODE_VERSION_STR) or greater and npm to build. You can get it at https://nodejs.org/en/download/"; \
|
||||
|
|
@ -433,7 +432,7 @@ lint: lint-frontend lint-backend
|
|||
lint-fix: lint-frontend-fix lint-backend-fix
|
||||
|
||||
.PHONY: lint-frontend
|
||||
lint-frontend: lint-js tsc lint-css
|
||||
lint-frontend: lint-js lint-css
|
||||
|
||||
.PHONY: lint-frontend-fix
|
||||
lint-frontend-fix: lint-js-fix lint-css-fix
|
||||
|
|
@ -466,7 +465,7 @@ lint-swagger: node_modules
|
|||
|
||||
.PHONY: lint-renovate
|
||||
lint-renovate: node_modules
|
||||
npx --yes --package $(RENOVATE_NPM_PACKAGE) -- renovate-config-validator --no-global .forgejo/renovate.json > .lint-renovate 2>&1 || true
|
||||
npx --yes --package $(RENOVATE_NPM_PACKAGE) -- renovate-config-validator > .lint-renovate 2>&1 || true
|
||||
@if grep --quiet --extended-regexp -e '^( ERROR:)' .lint-renovate ; then cat .lint-renovate ; rm .lint-renovate ; exit 1 ; fi
|
||||
@rm .lint-renovate
|
||||
|
||||
|
|
@ -476,7 +475,7 @@ lint-locale:
|
|||
|
||||
.PHONY: lint-locale-usage
|
||||
lint-locale-usage:
|
||||
$(GO) run ./build/lint-locale-usage/bin --allow-masked-usages-from=build/lint-locale-usage/allowed-masked-usage.txt
|
||||
$(GO) run ./build/lint-locale-usage --allow-masked-usages-from=build/lint-locale-usage/allowed-masked-usage.txt
|
||||
|
||||
.PHONY: lint-md
|
||||
lint-md: node_modules
|
||||
|
|
@ -486,23 +485,10 @@ RUN_DEADCODE = $(GO) run $(DEADCODE_PACKAGE) -generated=false -f='{{println .Pat
|
|||
|
||||
.PHONY: lint-go
|
||||
lint-go:
|
||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) \
|
||||
|| (code=$$?; echo "Please run 'make lint-go-fix' and commit the result"; exit $${code})
|
||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS)
|
||||
$(RUN_DEADCODE) > .cur-deadcode-out
|
||||
@$(DIFF) .deadcode-out .cur-deadcode-out >.deadcode.diff || true
|
||||
@if grep -qE '^[+][^+]' .deadcode.diff ; then \
|
||||
cat .deadcode.diff ; \
|
||||
echo "Looks like you added dead code, please evaluate and remove or use it."; \
|
||||
echo "If you are sure the dead code should stay around, please run 'make lint-go-fix',"; \
|
||||
echo "commit the result and explain the reason in the commit message / PR description."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if grep -qE '^[-][^-]' .deadcode.diff ; then \
|
||||
cat .deadcode.diff ; \
|
||||
echo "Looks like you removed dead code. Thank you!"; \
|
||||
echo "Run 'make lint-go-fix' and commit the result to accept."; \
|
||||
fi
|
||||
$(GO) run $(ERRORTYPE_PACKAGE) ./...
|
||||
@$(DIFF) .deadcode-out .cur-deadcode-out \
|
||||
|| (code=$$?; echo "Please run 'make lint-go-fix' and commit the result"; exit $${code})
|
||||
|
||||
.PHONY: lint-go-fix
|
||||
lint-go-fix:
|
||||
|
|
@ -530,15 +516,6 @@ lint-disposable-emails-fix:
|
|||
security-check:
|
||||
$(GO) run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||
|
||||
.PHONY: tsc
|
||||
tsc: node_modules
|
||||
npx tsc --noEmit
|
||||
|
||||
# target for PRs to be pushed. Mandatory to succeed in CI
|
||||
.PHONY: pr-go
|
||||
pr-go: deps-backend deps-tools lint-backend tidy-check swagger-check lint-swagger fmt-check swagger-validate
|
||||
TAGS=bindata $(MAKE) backend
|
||||
|
||||
###
|
||||
# Development and testing targets
|
||||
###
|
||||
|
|
@ -562,12 +539,12 @@ test: test-frontend test-backend
|
|||
.PHONY: test-backend
|
||||
test-backend: | compute-go-test-packages
|
||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||
@TZ=UTC GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
||||
@TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
|
||||
|
||||
.PHONY: test-remote-cacher
|
||||
test-remote-cacher:
|
||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||
GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES)
|
||||
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES)
|
||||
|
||||
.PHONY: test-frontend
|
||||
test-frontend: node_modules
|
||||
|
|
@ -592,7 +569,7 @@ test-check:
|
|||
.PHONY: test\#%
|
||||
test\#%: | compute-go-test-packages
|
||||
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
|
||||
@TZ=UTC GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
||||
@TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
|
||||
|
||||
coverage-merge:
|
||||
rm -fr coverage/merged ; mkdir -p coverage/merged
|
||||
|
|
@ -797,7 +774,7 @@ migrations.individual.mysql.test: $(GO_SOURCES) | compute-migration-packages
|
|||
|
||||
.PHONY: migrations.individual.sqlite.test\#%
|
||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$*
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||
|
||||
.PHONY: migrations.individual.pgsql.test
|
||||
migrations.individual.pgsql.test: $(GO_SOURCES) | compute-migration-packages
|
||||
|
|
@ -807,7 +784,7 @@ migrations.individual.pgsql.test: $(GO_SOURCES) | compute-migration-packages
|
|||
|
||||
.PHONY: migrations.individual.pgsql.test\#%
|
||||
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$*
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test
|
||||
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite | compute-migration-packages
|
||||
|
|
@ -817,7 +794,7 @@ migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite | compute-m
|
|||
|
||||
.PHONY: migrations.individual.sqlite.test\#%
|
||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$*
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$*
|
||||
|
||||
e2e.mysql.test: $(GO_SOURCES)
|
||||
$(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.mysql.test
|
||||
|
|
@ -968,13 +945,16 @@ deps-tools:
|
|||
$(GO) install $(XGO_PACKAGE)
|
||||
$(GO) install $(GO_LICENSES_PACKAGE)
|
||||
$(GO) install $(GOVULNCHECK_PACKAGE)
|
||||
$(GO) install $(ERRORTYPE_PACKAGE)
|
||||
$(GO) install $(MOCKERY_PACKAGE)
|
||||
$(GO) install $(GOMOCK_PACKAGE)
|
||||
|
||||
node_modules: package-lock.json
|
||||
npm install --no-save
|
||||
@touch node_modules
|
||||
|
||||
.venv: poetry.lock
|
||||
poetry install
|
||||
@touch .venv
|
||||
|
||||
.PHONY: fomantic
|
||||
fomantic:
|
||||
rm -rf $(FOMANTIC_WORK_DIR)/build
|
||||
|
|
@ -1024,9 +1004,9 @@ generate-license:
|
|||
generate-gitignore:
|
||||
$(GO) run build/generate-gitignores.go
|
||||
|
||||
.PHONY: generate-mockery
|
||||
generate-mockery:
|
||||
$(GO) run $(MOCKERY_PACKAGE)
|
||||
.PHONY: generate-gomock
|
||||
generate-gomock:
|
||||
$(GO) run $(GOMOCK_PACKAGE) -package mock -destination ./modules/queue/mock/redisuniversalclient.go forgejo.org/modules/nosql RedisClient
|
||||
|
||||
.PHONY: generate-images
|
||||
generate-images: | node_modules
|
||||
|
|
|
|||
|
|
@ -130,10 +130,6 @@ A [companion blog post](https://forgejo.org/2024-07-release-v8-0/) provides addi
|
|||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3334): <!--number 3334 --><!--number--><!--description Added support for the `workflow_dispatch` workflow trigger-->added support for the [`workflow_dispatch` trigger](https://forgejo.org/docs/v8.0/user/actions/#onworkflow_dispatch) in Forgejo Actions.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3307): <!--number 3307 --><!--number--><!--description Support [Proof Key for Code Exchange (PKCE - RFC7636)](https://www.rfc-editor.org/rfc/rfc7636) for external login using the OpenID Connect authentication source.-->support [Proof Key for Code Exchange (PKCE - RFC7636)](https://www.rfc-editor.org/rfc/rfc7636) for external login using the OpenID Connect authentication source.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3139): <!--number 3139 --><!--number--><!--description Allow hiding auto generated release archives-->allow hiding auto generated release archives.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3952): Update of Chroma from v2.13.0: to v2.14.0:
|
||||
- [`1e983e7`](https://github.com/alecthomas/chroma/commit/1e983e7) lexers/cue: support CUE attributes ([#​961](https://github.com/alecthomas/chroma/issues/961))
|
||||
- [`9347b55`](https://github.com/alecthomas/chroma/commit/9347b55) Add Gleam syntax highlighting ([#​959](https://github.com/alecthomas/chroma/issues/959))
|
||||
- [`2580aaa`](https://github.com/alecthomas/chroma/commit/2580aaa) Add Bazel bzlmod support into Python lexer ([#​947](https://github.com/alecthomas/chroma/issues/947))
|
||||
- **Bug fixes**
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4732) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4715)): <!--number 4732 --><!--number--><!--description -->Show the AGit label on merged pull requests.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4689) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4687)): <!--number 4689 --><!--number--><!--description -->Fixed: issue state change via the API is not idempotent.<!--description-->
|
||||
|
|
@ -150,9 +146,6 @@ A [companion blog post](https://forgejo.org/2024-07-release-v8-0/) provides addi
|
|||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3442): <!--number 3442 --><!--number--><!--description Save updated empty comments instead of skipping the update silently, [which prevented the removal of attachments of such comments](https://codeberg.org/forgejo/forgejo/issues/3424).-->Fixed: it is not possible to remove attachments from an empty comment.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3430): <!--number 3430 --><!--number--><!--description Fixed a bug where the `/api/v1/repos/{owner}/{repo}/wiki` API endpoints were using a hardcoded "master" branch for the wiki, rather than the branch they really use.-->Fixed: the `/api/v1/repos/{owner}/{repo}/wiki` API endpoints is using a hardcoded "master" branch for the wiki, rather than the branch they really use.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3379): <!--number 3379 --><!--number--><!--description -->Fixed: using the API to search for users, the results are not paged by default an the default paging limits are not respected.<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/3952): Update of Chroma from v2.13.0: to v2.14.0:
|
||||
- [`736c0ea`](https://github.com/alecthomas/chroma/commit/736c0ea) Typescript: Several fixes ([#​952](https://github.com/alecthomas/chroma/issues/952))
|
||||
- [`e5c25d0`](https://github.com/alecthomas/chroma/commit/e5c25d0) Org: Keep all newlines ([#​951](https://github.com/alecthomas/chroma/issues/951))
|
||||
- **Localization**
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4661) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4568)): <!--number 4661 --><!--number--><!--description -->24 July updates<!--description-->
|
||||
- [PR](https://codeberg.org/forgejo/forgejo/pulls/4565) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4451)): <!--number 4565 --><!--number--><!--description -->19 July updates<!--description-->
|
||||
|
|
|
|||
291
assets/go-licenses.json
generated
291
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
|
@ -74,7 +74,7 @@ func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error)
|
|||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/fixtures`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/gitea_migrations/fixtures`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/migrations/fixtures`))
|
||||
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`services/gitdiff/testdata`))
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +181,7 @@ func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string
|
|||
break
|
||||
}
|
||||
}
|
||||
return mainOptions, subCmd, subArgs
|
||||
return
|
||||
}
|
||||
|
||||
func showUsage() {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,49 @@ translation_meta.test
|
|||
# this also gets instantiated as a Messenger once
|
||||
repo.migrate.migrating_failed.error
|
||||
|
||||
# models/asymkey/gpg_key_object_verification.go: $ObjectVerification.Reason
|
||||
# unfortunately, it is non-trivial to parse all the occurences
|
||||
gpg.error.extract_sign
|
||||
gpg.error.failed_retrieval_gpg_keys
|
||||
gpg.error.generate_hash
|
||||
gpg.error.no_committer_account
|
||||
|
||||
# models/system/notice.go: func (n *Notice) TrStr() string
|
||||
admin.notices.type_1
|
||||
admin.notices.type_2
|
||||
|
||||
# modules/setting/ui.go
|
||||
themes.names.
|
||||
|
||||
# services/context/context.go
|
||||
relativetime.
|
||||
|
||||
# templates/mail/issue/default.tmpl: $.locale.Tr
|
||||
mail.issue.in_tree_path
|
||||
|
||||
# templates/package/metadata/arch.tmpl: $.locale.Tr
|
||||
packages.details.license
|
||||
|
||||
# templates/repo/issue/view_content.tmpl: indirection via $closeTranslationKey
|
||||
repo.issues.close
|
||||
repo.pulls.close
|
||||
|
||||
# templates/repo/issue/view_content/comments.tmpl: indirection via $refTr
|
||||
repo.issues.ref_closing_from
|
||||
repo.issues.ref_issue_from
|
||||
repo.issues.ref_pull_from
|
||||
repo.issues.ref_reopening_from
|
||||
|
||||
# templates/repo/issue/view_content/comments.tmpl: ctx.Locale.Tr (printf "projects.type-%d.display_name" .OldProject.Type)
|
||||
projects.
|
||||
projects.type-1.display_name
|
||||
projects.type-2.display_name
|
||||
projects.type-3.display_name
|
||||
|
||||
# templates/repo/settings/webhook/link_menu.tmpl, templates/webhook/new.tmpl: repo.settings.web_hook_name_
|
||||
# tests/integration/repo_archive_text_test.go
|
||||
repo.settings.
|
||||
|
||||
# services/migrations/migrate.go: messenger calls
|
||||
# ToDo: give them a unique prefix
|
||||
repo.migrate.
|
||||
|
|
|
|||
|
|
@ -1,208 +0,0 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
goParser "go/parser"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
llu "forgejo.org/build/lint-locale-usage"
|
||||
lluAsymKey "forgejo.org/models/asymkey/lint-locale-usage"
|
||||
lluUnit "forgejo.org/models/unit/lint-locale-usage"
|
||||
lluMigrate "forgejo.org/services/migrations/lint-locale-usage"
|
||||
)
|
||||
|
||||
// the `Handle*File` functions follow the following calling convention:
|
||||
// * `fname` is the name of the input file
|
||||
// * `src` is either `nil` (then the function invokes `ReadFile` to read the file)
|
||||
// or the contents of the file as {`[]byte`, or a `string`}
|
||||
|
||||
func HandleGoFile(handler llu.Handler, fname string, src any) error {
|
||||
fset := token.NewFileSet()
|
||||
node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution|goParser.ParseComments)
|
||||
if err != nil {
|
||||
return llu.LocatedError{
|
||||
Location: fname,
|
||||
Kind: "Go parser",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
return HandleGoNode(handler, fset, fname, n)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func HandleGoNode(handler llu.Handler, fset *token.FileSet, fname string, n ast.Node) bool {
|
||||
// search for function calls of the form `anything.Tr(any-string-lit, ...)`
|
||||
|
||||
switch n2 := n.(type) {
|
||||
case *ast.CallExpr:
|
||||
if len(n2.Args) == 0 {
|
||||
return true
|
||||
}
|
||||
funSel, ok := n2.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
var gotUnexpectedInvoke *int
|
||||
|
||||
for _, argNum := range ltf {
|
||||
if len(n2.Args) <= int(argNum) {
|
||||
argc := len(n2.Args)
|
||||
gotUnexpectedInvoke = &argc
|
||||
} else {
|
||||
handler.HandleGoTrArgument(fset, n2.Args[int(argNum)], "")
|
||||
}
|
||||
}
|
||||
|
||||
if gotUnexpectedInvoke != nil {
|
||||
handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke)
|
||||
}
|
||||
|
||||
case *ast.CompositeLit:
|
||||
if strings.HasSuffix(fname, "models/unit/unit.go") {
|
||||
lluUnit.HandleCompositeUnit(handler, fset, n2)
|
||||
} else if strings.Contains(fname, "models/asymkey/") {
|
||||
lluAsymKey.HandleCompositeErrorReason(handler, fset, n2)
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
if matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKeyWeak"); matchInsPrefix != nil {
|
||||
results := n2.Type.Results.List
|
||||
if len(results) != 1 {
|
||||
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
||||
// search for return stmts
|
||||
// TODO: what about nested functions?
|
||||
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||
for _, res := range ret.Results {
|
||||
ast.Inspect(res, func(n ast.Node) bool {
|
||||
if expr, ok := n.(ast.Expr); ok {
|
||||
handler.HandleGoTrArgument(fset, expr, *matchInsPrefix)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
if matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKey"); matchInsPrefix != nil {
|
||||
results := n2.Type.Results.List
|
||||
if len(results) != 1 {
|
||||
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
||||
// search for return stmts
|
||||
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||
for _, res := range ret.Results {
|
||||
handler.HandleGoTrArgument(fset, res, *matchInsPrefix)
|
||||
}
|
||||
return false
|
||||
} else if _, ok := n.(*ast.FuncDecl); ok {
|
||||
ast.Inspect(n, func(n2 ast.Node) bool {
|
||||
return HandleGoNode(handler, fset, fname, n2)
|
||||
})
|
||||
// don't search inside nested functions for return stmts
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
if strings.HasSuffix(fname, "services/migrations/migrate.go") {
|
||||
lluMigrate.HandleMessengerInFunc(handler, fset, n2)
|
||||
}
|
||||
return true
|
||||
case *ast.GenDecl:
|
||||
switch n2.Tok {
|
||||
case token.CONST, token.VAR:
|
||||
matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, " llu:TrKeys")
|
||||
if matchInsPrefix == nil {
|
||||
return true
|
||||
}
|
||||
for _, spec := range n2.Specs {
|
||||
// interpret all contained strings as message IDs
|
||||
ast.Inspect(spec, func(n ast.Node) bool {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.HandleGoTrBasicLit(fset, argLit, *matchInsPrefix)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
case token.TYPE:
|
||||
// modules/web/middleware/binding.go:Validate uses the convention that structs
|
||||
// entries can have tags.
|
||||
// In particular, `locale:$msgid` should be handled; any fields with `form:-` shouldn't.
|
||||
// Problem: we don't know which structs are forms, actually.
|
||||
|
||||
for _, spec := range n2.Specs {
|
||||
tspec := spec.(*ast.TypeSpec)
|
||||
structNode, ok := tspec.Type.(*ast.StructType)
|
||||
if !ok || !(strings.HasSuffix(tspec.Name.Name, "Form") ||
|
||||
(tspec.Doc != nil &&
|
||||
slices.ContainsFunc(tspec.Doc.List, func(c *ast.Comment) bool {
|
||||
return c.Text == "// swagger:model"
|
||||
}))) {
|
||||
continue
|
||||
}
|
||||
for _, field := range structNode.Fields.List {
|
||||
if field.Names == nil {
|
||||
continue
|
||||
}
|
||||
if len(field.Names) != 1 {
|
||||
handler.OnWarning(fset, field.Type.Pos(), "unsupported multiple field names")
|
||||
continue
|
||||
}
|
||||
msgidPos := field.Names[0].NamePos
|
||||
msgid := "form." + field.Names[0].Name
|
||||
if field.Tag != nil && field.Tag.Kind == token.STRING {
|
||||
rawTag, err := strconv.Unquote(field.Tag.Value)
|
||||
if err != nil {
|
||||
handler.OnWarning(fset, field.Tag.ValuePos, "invalid tag value encountered")
|
||||
continue
|
||||
}
|
||||
tag := reflect.StructTag(rawTag)
|
||||
if tag.Get("form") == "-" {
|
||||
continue
|
||||
}
|
||||
tmp := tag.Get("locale")
|
||||
if len(tmp) != 0 {
|
||||
msgidPos = field.Tag.ValuePos
|
||||
msgid = tmp
|
||||
}
|
||||
}
|
||||
handler.OnMsgid(fset, msgidPos, msgid, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -2,17 +2,18 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package lintLocaleUsage
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
goParser "go/parser"
|
||||
"go/token"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (handler Handler) HandleGoTrBasicLit(fset *token.FileSet, argLit *ast.BasicLit, prefix string) {
|
||||
func (handler Handler) handleGoTrBasicLit(fset *token.FileSet, argLit *ast.BasicLit, prefix string) {
|
||||
if argLit.Kind == token.STRING {
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(argLit.Value)
|
||||
|
|
@ -28,20 +29,18 @@ func (handler Handler) HandleGoTrBasicLit(fset *token.FileSet, argLit *ast.Basic
|
|||
}
|
||||
handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc)
|
||||
} else {
|
||||
handler.OnMsgid(fset, argLit.ValuePos, arg, false)
|
||||
handler.OnMsgid(fset, argLit.ValuePos, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) {
|
||||
switch n := n.(type) {
|
||||
case *ast.BasicLit:
|
||||
handler.HandleGoTrBasicLit(fset, n, prefix)
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
if n.Op != token.ADD {
|
||||
func (handler Handler) handleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.handleGoTrBasicLit(fset, argLit, prefix)
|
||||
} else if argBinExpr, ok := n.(*ast.BinaryExpr); ok {
|
||||
if argBinExpr.Op != token.ADD {
|
||||
// pass
|
||||
} else if argLit, ok := n.X.(*ast.BasicLit); ok && argLit.Kind == token.STRING {
|
||||
} else if argLit, ok := argBinExpr.X.(*ast.BasicLit); ok && argLit.Kind == token.STRING {
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(argLit.Value)
|
||||
if err != nil {
|
||||
|
|
@ -55,43 +54,10 @@ func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr, prefi
|
|||
}
|
||||
handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc)
|
||||
}
|
||||
|
||||
case *ast.CallExpr:
|
||||
if selExpr, ok := n.Fun.(*ast.SelectorExpr); ok {
|
||||
if xIdent, xok := selExpr.X.(*ast.Ident); !xok || xIdent.Name != "fmt" {
|
||||
return
|
||||
}
|
||||
if selExpr.Sel.Name != "Sprintf" {
|
||||
handler.OnWarning(fset, selExpr.Sel.NamePos, fmt.Sprintf("unexpected formatting function encountered: %s", selExpr.Sel.Name))
|
||||
return
|
||||
}
|
||||
if len(n.Args) == 0 {
|
||||
handler.OnWarning(fset, selExpr.Sel.NamePos, fmt.Sprintf("unexpected formatting function invocation (no arguments) of '%s'", selExpr.Sel.Name))
|
||||
return
|
||||
}
|
||||
|
||||
if argLit, ok := n.Args[0].(*ast.BasicLit); ok && argLit.Kind == token.STRING {
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(argLit.Value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if strings.Contains(arg, " ") {
|
||||
handler.OnWarning(fset, argLit.ValuePos, fmt.Sprintf(
|
||||
"formatting function invocation of '%s' with weird msgid format string: %s",
|
||||
selExpr.Sel.Name,
|
||||
arg,
|
||||
))
|
||||
return
|
||||
}
|
||||
// found interesting strings
|
||||
handler.OnMsgidPattern(fset, argLit.ValuePos, prefix+arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (handler Handler) HandleGoCommentGroup(fset *token.FileSet, cg *ast.CommentGroup, commentPrefix string) *string {
|
||||
func (handler Handler) handleGoCommentGroup(fset *token.FileSet, cg *ast.CommentGroup, commentPrefix string) *string {
|
||||
if cg == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -121,3 +87,131 @@ func (handler Handler) HandleGoCommentGroup(fset *token.FileSet, cg *ast.Comment
|
|||
return &matchInsPrefix
|
||||
}
|
||||
}
|
||||
|
||||
// the `Handle*File` functions follow the following calling convention:
|
||||
// * `fname` is the name of the input file
|
||||
// * `src` is either `nil` (then the function invokes `ReadFile` to read the file)
|
||||
// or the contents of the file as {`[]byte`, or a `string`}
|
||||
|
||||
func (handler Handler) HandleGoFile(fname string, src any) error {
|
||||
fset := token.NewFileSet()
|
||||
node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution|goParser.ParseComments)
|
||||
if err != nil {
|
||||
return LocatedError{
|
||||
Location: fname,
|
||||
Kind: "Go parser",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
// search for function calls of the form `anything.Tr(any-string-lit, ...)`
|
||||
|
||||
switch n2 := n.(type) {
|
||||
case *ast.CallExpr:
|
||||
if len(n2.Args) == 0 {
|
||||
return true
|
||||
}
|
||||
funSel, ok := n2.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
var gotUnexpectedInvoke *int
|
||||
|
||||
for _, argNum := range ltf {
|
||||
if len(n2.Args) <= int(argNum) {
|
||||
argc := len(n2.Args)
|
||||
gotUnexpectedInvoke = &argc
|
||||
} else {
|
||||
handler.handleGoTrArgument(fset, n2.Args[int(argNum)], "")
|
||||
}
|
||||
}
|
||||
|
||||
if gotUnexpectedInvoke != nil {
|
||||
handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke)
|
||||
}
|
||||
case *ast.CompositeLit:
|
||||
ident, ok := n2.Type.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// special case: models/unit/unit.go
|
||||
if strings.HasSuffix(fname, "unit.go") && ident.Name == "Unit" {
|
||||
if len(n2.Elts) != 6 {
|
||||
handler.OnWarning(fset, n2.Pos(), "unexpected initialization of 'Unit' (unexpected number of arguments)")
|
||||
}
|
||||
// NameKey has index 2
|
||||
// invoked like '{{ctx.Locale.Tr $unit.NameKey}}'
|
||||
nameKey, ok := n2.Elts[2].(*ast.BasicLit)
|
||||
if !ok || nameKey.Kind != token.STRING {
|
||||
handler.OnWarning(fset, n2.Elts[2].Pos(), "unexpected initialization of 'Unit' (expected string literal as NameKey)")
|
||||
return true
|
||||
}
|
||||
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(nameKey.Value)
|
||||
if err == nil {
|
||||
// found interesting strings
|
||||
handler.OnMsgid(fset, nameKey.ValuePos, arg)
|
||||
}
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
matchInsPrefix := handler.handleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKey")
|
||||
if matchInsPrefix == nil {
|
||||
return true
|
||||
}
|
||||
results := n2.Type.Results.List
|
||||
if len(results) != 1 {
|
||||
handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name))
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Inspect(n2.Body, func(n ast.Node) bool {
|
||||
// search for return stmts
|
||||
// TODO: what about nested functions?
|
||||
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||
for _, res := range ret.Results {
|
||||
ast.Inspect(res, func(n ast.Node) bool {
|
||||
if expr, ok := n.(ast.Expr); ok {
|
||||
handler.handleGoTrArgument(fset, expr, *matchInsPrefix)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true
|
||||
case *ast.GenDecl:
|
||||
if !(n2.Tok == token.CONST || n2.Tok == token.VAR) {
|
||||
return true
|
||||
}
|
||||
matchInsPrefix := handler.handleGoCommentGroup(fset, n2.Doc, " llu:TrKeys")
|
||||
if matchInsPrefix == nil {
|
||||
return true
|
||||
}
|
||||
for _, spec := range n2.Specs {
|
||||
// interpret all contained strings as message IDs
|
||||
ast.Inspect(spec, func(n ast.Node) bool {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.handleGoTrBasicLit(fset, argLit, *matchInsPrefix)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package lintLocaleUsage
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -48,33 +48,18 @@ func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.N
|
|||
}
|
||||
|
||||
funcname := ""
|
||||
switch nodeCommand.Args[0].Type() {
|
||||
case tmplParser.NodeChain:
|
||||
nodeChain := nodeCommand.Args[0].(*tmplParser.ChainNode)
|
||||
if nodeChain, ok := nodeCommand.Args[0].(*tmplParser.ChainNode); ok {
|
||||
if nodeIdent, ok := nodeChain.Node.(*tmplParser.IdentifierNode); ok {
|
||||
if nodeIdent.Ident != "ctx" || len(nodeChain.Field) != 2 || nodeChain.Field[0] != "Locale" {
|
||||
return
|
||||
}
|
||||
funcname = nodeChain.Field[1]
|
||||
}
|
||||
|
||||
case tmplParser.NodeField:
|
||||
nodeField := nodeCommand.Args[0].(*tmplParser.FieldNode)
|
||||
if len(nodeField.Ident) != 2 || nodeField.Ident[0] != "locale" {
|
||||
} else if nodeField, ok := nodeCommand.Args[0].(*tmplParser.FieldNode); ok {
|
||||
if len(nodeField.Ident) != 2 || !(nodeField.Ident[0] == "locale" || nodeField.Ident[0] == "Locale") {
|
||||
return
|
||||
}
|
||||
resolvedPos := fset.PositionFor(token.Pos(nodeCommand.Pos), false)
|
||||
if !strings.Contains(resolvedPos.Filename, "templates/mail/") {
|
||||
handler.OnWarning(fset, token.Pos(nodeCommand.Pos), "encountered unexpected .locale usage")
|
||||
}
|
||||
funcname = nodeField.Ident[1]
|
||||
|
||||
case tmplParser.NodeVariable:
|
||||
nodeVar := nodeCommand.Args[0].(*tmplParser.VariableNode)
|
||||
if len(nodeVar.Ident) != 3 || !(nodeVar.Ident[0] == "$" && nodeVar.Ident[1] == "locale") {
|
||||
return
|
||||
}
|
||||
funcname = nodeVar.Ident[2]
|
||||
}
|
||||
|
||||
var gotUnexpectedInvoke *int
|
||||
|
|
@ -108,7 +93,7 @@ func (handler Handler) handleTemplateMsgid(fset *token.FileSet, node tmplParser.
|
|||
case tmplParser.NodeString:
|
||||
nodeString := node.(*tmplParser.StringNode)
|
||||
// found interesting strings
|
||||
handler.OnMsgid(fset, pos, nodeString.Text, false)
|
||||
handler.OnMsgid(fset, pos, nodeString.Text)
|
||||
|
||||
case tmplParser.NodePipe:
|
||||
nodePipe := node.(*tmplParser.PipeNode)
|
||||
|
|
@ -147,15 +132,19 @@ func (handler Handler) handleTemplateMsgid(fset *token.FileSet, node tmplParser.
|
|||
|
||||
if len(nodeCommand.Args) == 2 {
|
||||
// found interesting strings
|
||||
handler.OnMsgid(fset, stringPos, msgidPrefix, false)
|
||||
handler.OnMsgid(fset, stringPos, msgidPrefix)
|
||||
} else {
|
||||
if nodeIdent.Ident == "printf" {
|
||||
// found interesting strings
|
||||
if !(strings.HasSuffix(msgidPrefix, ".%s") && strings.Count(msgidPrefix, "%") == 1) {
|
||||
handler.OnMsgidPattern(fset, stringPos, msgidPrefix)
|
||||
parts := strings.SplitN(msgidPrefix, "%", 2)
|
||||
if len(parts) != 2 {
|
||||
handler.OnWarning(
|
||||
fset,
|
||||
stringPos,
|
||||
fmt.Sprintf("unsupported invocation of locate function (format string doesn't match \"prefix%%smth\" pattern): %s", nodeString.String()),
|
||||
)
|
||||
return
|
||||
}
|
||||
msgidPrefix = strings.TrimSuffix(msgidPrefix, "%s")
|
||||
msgidPrefix = parts[0]
|
||||
}
|
||||
|
||||
msgidPrefixFin, truncated := PrepareMsgidPrefix(msgidPrefix)
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package lintLocaleUsage
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LocatedError struct {
|
||||
Location string
|
||||
Kind string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e LocatedError) Error() string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(e.Location)
|
||||
sb.WriteString(":\t")
|
||||
if e.Kind != "" {
|
||||
sb.WriteString(e.Kind)
|
||||
sb.WriteString(": ")
|
||||
}
|
||||
sb.WriteString("ERROR: ")
|
||||
sb.WriteString(e.Err.Error())
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func InitLocaleTrFunctions() map[string][]uint {
|
||||
ret := make(map[string][]uint)
|
||||
|
||||
f0 := []uint{0}
|
||||
ret["Tr"] = f0
|
||||
ret["TrString"] = f0
|
||||
ret["TrHTML"] = f0
|
||||
|
||||
ret["TrPluralString"] = []uint{1}
|
||||
ret["TrN"] = []uint{1, 2}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string, weak bool)
|
||||
OnMsgidPrefix func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool)
|
||||
OnMsgidPattern func(fset *token.FileSet, pos token.Pos, msgidPattern string)
|
||||
OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int)
|
||||
OnWarning func(fset *token.FileSet, pos token.Pos, msg string)
|
||||
LocaleTrFunctions map[string][]uint
|
||||
}
|
||||
|
||||
// Truncating a message id prefix to the last dot
|
||||
func PrepareMsgidPrefix(s string) (string, bool) {
|
||||
index := strings.LastIndexByte(s, 0x2e)
|
||||
if index == -1 {
|
||||
return "", true
|
||||
}
|
||||
return s[:index], index != len(s)-1
|
||||
}
|
||||
|
|
@ -13,11 +13,9 @@ import (
|
|||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
llu "forgejo.org/build/lint-locale-usage"
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/translation/localeiter"
|
||||
)
|
||||
|
|
@ -25,6 +23,27 @@ import (
|
|||
// this works by first gathering all valid source string IDs from `en-US` reference files
|
||||
// and then checking if all used source strings are actually defined
|
||||
|
||||
type LocatedError struct {
|
||||
Location string
|
||||
Kind string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e LocatedError) Error() string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(e.Location)
|
||||
sb.WriteString(":\t")
|
||||
if e.Kind != "" {
|
||||
sb.WriteString(e.Kind)
|
||||
sb.WriteString(": ")
|
||||
}
|
||||
sb.WriteString("ERROR: ")
|
||||
sb.WriteString(e.Err.Error())
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func InitLocaleTrFunctions() map[string][]uint {
|
||||
ret := make(map[string][]uint)
|
||||
|
||||
|
|
@ -39,63 +58,26 @@ func InitLocaleTrFunctions() map[string][]uint {
|
|||
return ret
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string)
|
||||
OnMsgidPrefix func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool)
|
||||
OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int)
|
||||
OnWarning func(fset *token.FileSet, pos token.Pos, msg string)
|
||||
LocaleTrFunctions map[string][]uint
|
||||
}
|
||||
|
||||
type StringTrie interface {
|
||||
Matches(key []string) bool
|
||||
}
|
||||
|
||||
type StringTrieMap map[string]StringTrie
|
||||
|
||||
func printfPatternToRegex(key string) (string, bool) {
|
||||
parts := strings.Split(key, "%")
|
||||
if len(parts) < 2 {
|
||||
return key, false
|
||||
}
|
||||
var pattern strings.Builder
|
||||
pattern.WriteString("^")
|
||||
pattern.WriteString(parts[0])
|
||||
skip := false
|
||||
for _, part := range parts[1:] {
|
||||
if skip {
|
||||
skip = false
|
||||
continue
|
||||
}
|
||||
if len(part) == 0 {
|
||||
// "%%"
|
||||
pattern.WriteString("%")
|
||||
continue
|
||||
}
|
||||
switch part[0] {
|
||||
case 'd':
|
||||
pattern.WriteString("[0-9]+")
|
||||
default:
|
||||
pattern.WriteString("[A-Za-z0-9]*")
|
||||
}
|
||||
pattern.WriteString(part[1:])
|
||||
}
|
||||
pattern.WriteString("$")
|
||||
return pattern.String(), true
|
||||
}
|
||||
|
||||
func (m StringTrieMap) Matches(key []string) bool {
|
||||
if len(key) == 0 || m == nil {
|
||||
return true
|
||||
}
|
||||
value, ok := m[key[0]]
|
||||
if !ok {
|
||||
for altKey, value := range m {
|
||||
// TODO: cache mapping $printfFormatString -> $regexpCompileOutput
|
||||
pattern, found := printfPatternToRegex(altKey)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
matched, err := regexp.MatchString(pattern, key[0])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unable to compile regexp '%s': %s", pattern, err.Error()))
|
||||
}
|
||||
if matched && (value == nil || value.Matches(key[1:])) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if value == nil {
|
||||
|
|
@ -131,7 +113,7 @@ func (m StringTrieMap) Insert(key []string) {
|
|||
func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], allowedMaskedPrefixes StringTrieMap, chkMsgid func(msgid string) bool) error {
|
||||
file, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return llu.LocatedError{
|
||||
return LocatedError{
|
||||
Location: fname,
|
||||
Kind: "Open",
|
||||
Err: err,
|
||||
|
|
@ -147,11 +129,11 @@ func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], al
|
|||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if linePrefix, found := strings.CutSuffix(line, "."); found || strings.Contains(line, "%") {
|
||||
if linePrefix, found := strings.CutSuffix(line, "."); found {
|
||||
allowedMaskedPrefixes.Insert(strings.Split(linePrefix, "."))
|
||||
} else {
|
||||
if !chkMsgid(line) {
|
||||
return llu.LocatedError{
|
||||
return LocatedError{
|
||||
Location: fmt.Sprintf("%s: line %d", fname, lno),
|
||||
Kind: "undefined msgid",
|
||||
Err: errors.New(line),
|
||||
|
|
@ -161,7 +143,7 @@ func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], al
|
|||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return llu.LocatedError{
|
||||
return LocatedError{
|
||||
Location: fname,
|
||||
Kind: "Scanner",
|
||||
Err: err,
|
||||
|
|
@ -170,6 +152,15 @@ func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], al
|
|||
return nil
|
||||
}
|
||||
|
||||
// Truncating a message id prefix to the last dot
|
||||
func PrepareMsgidPrefix(s string) (string, bool) {
|
||||
index := strings.LastIndexByte(s, 0x2e)
|
||||
if index == -1 {
|
||||
return "", true
|
||||
}
|
||||
return s[:index], index != len(s)-1
|
||||
}
|
||||
|
||||
func Usage() {
|
||||
outp := flag.CommandLine.Output()
|
||||
fmt.Fprintf(outp, "Usage of %s:\n", os.Args[0])
|
||||
|
|
@ -191,14 +182,9 @@ func Usage() {
|
|||
|
||||
fmt.Fprintf(outp, "\nSpecial Go doc comments:\n")
|
||||
for _, i := range []string{
|
||||
"//llu:returnsTrKeyWeak",
|
||||
"\tcan be used in front of functions to indicate",
|
||||
"\tthat the function returns message IDs (allows nesting inside complicated function calls)",
|
||||
"\tWARNING: this currently doesn't support nested functions properly",
|
||||
"",
|
||||
"//llu:returnsTrKey",
|
||||
"\tcan be used in front of functions to indicate",
|
||||
"\tthat the function returns message IDs (doesn't allow nesting inside complicated function calls)",
|
||||
"\tthat the function returns message IDs",
|
||||
"\tWARNING: this currently doesn't support nested functions properly",
|
||||
"",
|
||||
"//llu:returnsTrKeySuffix prefix.",
|
||||
|
|
@ -224,7 +210,6 @@ func Usage() {
|
|||
func main() {
|
||||
allowMissingMsgids := false
|
||||
allowUnusedMsgids := false
|
||||
allowWeakMissingMsgids := true
|
||||
usedMsgids := make(container.Set[string])
|
||||
allowedMaskedPrefixes := make(StringTrieMap)
|
||||
|
||||
|
|
@ -243,12 +228,6 @@ func main() {
|
|||
false,
|
||||
"don't return an error code if missing message IDs are found",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&allowWeakMissingMsgids,
|
||||
"allow-weak-missing-msgids",
|
||||
true,
|
||||
"Don't return an error code if missing 'weak' (e.g. \"form.$msgid\") message IDs are found",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&allowUnusedMsgids,
|
||||
"allow-unused-msgids",
|
||||
|
|
@ -310,11 +289,7 @@ func main() {
|
|||
os.Exit(3)
|
||||
}
|
||||
|
||||
handler := llu.Handler{
|
||||
OnMsgidPattern: func(fset *token.FileSet, pos token.Pos, msgidPattern string) {
|
||||
msgidPatternSplit := strings.Split(msgidPattern, ".")
|
||||
allowedMaskedPrefixes.Insert(msgidPatternSplit)
|
||||
},
|
||||
handler := Handler{
|
||||
OnMsgidPrefix: func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool) {
|
||||
msgidPrefixSplit := strings.Split(msgidPrefix, ".")
|
||||
if !truncated {
|
||||
|
|
@ -324,15 +299,8 @@ func main() {
|
|||
fmt.Printf("%s:\tmissing msgid prefix: %s\n", fset.Position(pos).String(), msgidPrefix)
|
||||
}
|
||||
},
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) {
|
||||
if strings.Contains(msgid, "%") {
|
||||
fmt.Printf("%s:\tunexpected msgid pattern: %s\n", fset.Position(pos).String(), msgid)
|
||||
return
|
||||
}
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) {
|
||||
if !msgids.Contains(msgid) {
|
||||
if weak && allowWeakMissingMsgids {
|
||||
return
|
||||
}
|
||||
gotAnyMsgidError = true
|
||||
fmt.Printf("%s:\tmissing msgid: %s\n", fset.Position(pos).String(), msgid)
|
||||
} else {
|
||||
|
|
@ -346,7 +314,7 @@ func main() {
|
|||
OnWarning: func(fset *token.FileSet, pos token.Pos, msg string) {
|
||||
fmt.Printf("%s:\tWARNING: %s\n", fset.Position(pos).String(), msg)
|
||||
},
|
||||
LocaleTrFunctions: llu.InitLocaleTrFunctions(),
|
||||
LocaleTrFunctions: InitLocaleTrFunctions(),
|
||||
}
|
||||
|
||||
if err := filepath.WalkDir(".", func(fpath string, d fs.DirEntry, err error) error {
|
||||
|
|
@ -361,10 +329,10 @@ func main() {
|
|||
if name == "docker" || name == ".git" || name == "node_modules" {
|
||||
return fs.SkipDir
|
||||
}
|
||||
} else if name == "bindata.go" || fpath == "modules/translation/i18n/i18n_test.go" || fpath == "modules/translation/i18n/i18n_ini_test.go" {
|
||||
} else if name == "bindata.go" || fpath == "modules/translation/i18n/i18n_test.go" {
|
||||
// skip false positives
|
||||
} else if strings.HasSuffix(name, ".go") {
|
||||
onError(HandleGoFile(handler, fpath, nil))
|
||||
onError(handler.HandleGoFile(fpath, nil))
|
||||
} else if strings.HasSuffix(name, ".tmpl") {
|
||||
if strings.HasPrefix(fpath, "tests") && strings.HasSuffix(name, ".ini.tmpl") {
|
||||
// skip false positives
|
||||
|
|
@ -7,26 +7,24 @@ import (
|
|||
"go/token"
|
||||
"testing"
|
||||
|
||||
llu "forgejo.org/build/lint-locale-usage"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func buildHandler(ret *[]string) llu.Handler {
|
||||
return llu.Handler{
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) {
|
||||
func buildHandler(ret *[]string) Handler {
|
||||
return Handler{
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) {
|
||||
*ret = append(*ret, msgid)
|
||||
},
|
||||
OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) {},
|
||||
LocaleTrFunctions: llu.InitLocaleTrFunctions(),
|
||||
LocaleTrFunctions: InitLocaleTrFunctions(),
|
||||
}
|
||||
}
|
||||
|
||||
func HandleGoFileWrapped(t *testing.T, fname, src string) []string {
|
||||
var ret []string
|
||||
handler := buildHandler(&ret)
|
||||
require.NoError(t, HandleGoFile(handler, fname, src))
|
||||
require.NoError(t, handler.HandleGoFile(fname, src))
|
||||
return ret
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +28,6 @@ func subcmdActionsGenRunnerToken() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "generate-runner-token",
|
||||
Usage: "Generate a new token for a runner to use to register with the server",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateActionsRunnerToken,
|
||||
Aliases: []string{"grt"},
|
||||
Flags: []cli.Flag{
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ func subcmdRepoSyncReleases() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "repo-sync-releases",
|
||||
Usage: "Synchronize repository releases with tags",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRepoSyncReleases,
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +46,7 @@ func subcmdRegenerate() *cli.Command {
|
|||
Name: "regenerate",
|
||||
Usage: "Regenerate specific files",
|
||||
Commands: []*cli.Command{
|
||||
microcmdRegenHooks,
|
||||
microcmdRegenKeys,
|
||||
},
|
||||
}
|
||||
|
|
@ -63,8 +63,6 @@ func subcmdAuth() *cli.Command {
|
|||
microcmdAuthUpdateLdapBindDn(),
|
||||
microcmdAuthAddLdapSimpleAuth(),
|
||||
microcmdAuthUpdateLdapSimpleAuth(),
|
||||
microcmdAuthAddPAM(),
|
||||
microcmdAuthUpdatePAM(),
|
||||
microcmdAuthAddSMTP(),
|
||||
microcmdAuthUpdateSMTP(),
|
||||
microcmdAuthList(),
|
||||
|
|
@ -77,7 +75,6 @@ func subcmdSendMail() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "sendmail",
|
||||
Usage: "Send a message to all users",
|
||||
Before: noDanglingArgs,
|
||||
Action: runSendMail,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
@ -106,7 +103,7 @@ func idFlag() *cli.Int64Flag {
|
|||
}
|
||||
}
|
||||
|
||||
func runRepoSyncReleases(ctx context.Context, c *cli.Command) error {
|
||||
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ func microcmdAuthDelete() *cli.Command {
|
|||
Name: "delete",
|
||||
Usage: "Delete specific auth source",
|
||||
Flags: []cli.Flag{idFlag()},
|
||||
Before: noDanglingArgs,
|
||||
Action: runDeleteAuth,
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +39,6 @@ func microcmdAuthList() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List auth sources",
|
||||
Before: noDanglingArgs,
|
||||
Action: runListAuth,
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
|
|
|
|||
|
|
@ -133,9 +133,8 @@ func ldapSimpleAuthCLIFlags() []cli.Flag {
|
|||
|
||||
func microcmdAuthAddLdapBindDn() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "add-ldap",
|
||||
Usage: "Add new LDAP (via Bind DN) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Name: "add-ldap",
|
||||
Usage: "Add new LDAP (via Bind DN) authentication source",
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().addLdapBindDn(ctx, cli)
|
||||
},
|
||||
|
|
@ -145,9 +144,8 @@ func microcmdAuthAddLdapBindDn() *cli.Command {
|
|||
|
||||
func microcmdAuthUpdateLdapBindDn() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update-ldap",
|
||||
Usage: "Update existing LDAP (via Bind DN) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Name: "update-ldap",
|
||||
Usage: "Update existing LDAP (via Bind DN) authentication source",
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().updateLdapBindDn(ctx, cli)
|
||||
},
|
||||
|
|
@ -157,9 +155,8 @@ func microcmdAuthUpdateLdapBindDn() *cli.Command {
|
|||
|
||||
func microcmdAuthAddLdapSimpleAuth() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "add-ldap-simple",
|
||||
Usage: "Add new LDAP (simple auth) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Name: "add-ldap-simple",
|
||||
Usage: "Add new LDAP (simple auth) authentication source",
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().addLdapSimpleAuth(ctx, cli)
|
||||
},
|
||||
|
|
@ -169,9 +166,8 @@ func microcmdAuthAddLdapSimpleAuth() *cli.Command {
|
|||
|
||||
func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update-ldap-simple",
|
||||
Usage: "Update existing LDAP (simple auth) authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Name: "update-ldap-simple",
|
||||
Usage: "Update existing LDAP (simple auth) authentication source",
|
||||
Action: func(ctx context.Context, cli *cli.Command) error {
|
||||
return newAuthService().updateLdapSimpleAuth(ctx, cli)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -129,20 +129,6 @@ func oauthCLIFlags() []cli.Flag {
|
|||
Name: "allow-username-change",
|
||||
Usage: "Allow users to change their username",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "quota-group-claim-name",
|
||||
Value: "",
|
||||
Usage: "Claim name providing quota group names for this source",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "quota-group-map",
|
||||
Value: "",
|
||||
Usage: "JSON mapping between groups and quota groups",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "quota-group-map-removal",
|
||||
Usage: "Activate automatic quota group removal depending on groups",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +136,6 @@ func microcmdAuthAddOauth() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "add-oauth",
|
||||
Usage: "Add new Oauth authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: newAuthService().addOauth,
|
||||
Flags: oauthCLIFlags(),
|
||||
}
|
||||
|
|
@ -160,7 +145,6 @@ func microcmdAuthUpdateOauth() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "update-oauth",
|
||||
Usage: "Update existing Oauth authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: newAuthService().updateOauth,
|
||||
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{idFlag()}, oauthCLIFlags()[1:]...)...),
|
||||
}
|
||||
|
|
@ -197,9 +181,6 @@ func parseOAuth2Config(_ context.Context, c *cli.Command) *oauth2.Source {
|
|||
GroupTeamMap: c.String("group-team-map"),
|
||||
GroupTeamMapRemoval: c.Bool("group-team-map-removal"),
|
||||
AllowUsernameChange: c.Bool("allow-username-change"),
|
||||
QuotaGroupClaimName: c.String("quota-group-claim-name"),
|
||||
QuotaGroupMap: c.String("quota-group-map"),
|
||||
QuotaGroupMapRemoval: c.Bool("quota-group-map-removal"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -300,15 +281,6 @@ func (a *authService) updateOauth(ctx context.Context, c *cli.Command) error {
|
|||
if c.IsSet("group-team-map-removal") {
|
||||
oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
|
||||
}
|
||||
if c.IsSet("quota-group-claim-name") {
|
||||
oAuth2Config.QuotaGroupClaimName = c.String("quota-group-claim-name")
|
||||
}
|
||||
if c.IsSet("quota-group-map") {
|
||||
oAuth2Config.QuotaGroupMap = c.String("quota-group-map")
|
||||
}
|
||||
if c.IsSet("quota-group-map-removal") {
|
||||
oAuth2Config.QuotaGroupMapRemoval = c.Bool("quota-group-map-removal")
|
||||
}
|
||||
|
||||
if c.IsSet("allow-username-change") {
|
||||
oAuth2Config.AllowUsernameChange = c.Bool("allow-username-change")
|
||||
|
|
|
|||
|
|
@ -56,9 +56,6 @@ func TestAddOauth(t *testing.T) {
|
|||
"--group-team-map", `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`,
|
||||
"--group-team-map-removal",
|
||||
"--allow-username-change",
|
||||
"--quota-group-claim-name", "quota_groups",
|
||||
"--quota-group-map", `{"oauth_group_1": ["quota_group_1"], "oauth_group_2": ["quota_group_2"]}`,
|
||||
"--quota-group-map-removal",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
|
|
@ -85,9 +82,6 @@ func TestAddOauth(t *testing.T) {
|
|||
AdminGroup: "admin",
|
||||
GroupTeamMap: `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`,
|
||||
GroupTeamMapRemoval: true,
|
||||
QuotaGroupClaimName: "quota_groups",
|
||||
QuotaGroupMap: `{"oauth_group_1": ["quota_group_1"], "oauth_group_2": ["quota_group_2"]}`,
|
||||
QuotaGroupMapRemoval: true,
|
||||
RestrictedGroup: "restricted",
|
||||
SkipLocalTwoFA: true,
|
||||
AllowUsernameChange: true,
|
||||
|
|
@ -195,94 +189,6 @@ func TestAddOauth(t *testing.T) {
|
|||
},
|
||||
errMsg: "invalid Auto Discovery URL: example.com (this must be a valid URL starting with http:// or https://)",
|
||||
},
|
||||
// case 7
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--name", "oauth2 source with quota group claim name",
|
||||
"--provider", "openidConnect",
|
||||
"--auto-discover-url", "https://example.com/.well-known/openid-configuration",
|
||||
"--quota-group-claim-name", "quota_groups",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 source with quota group claim name",
|
||||
IsActive: true,
|
||||
Cfg: &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration",
|
||||
Scopes: []string{},
|
||||
QuotaGroupClaimName: "quota_groups",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 8
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--name", "oauth2 source with quota group map",
|
||||
"--provider", "openidConnect",
|
||||
"--auto-discover-url", "https://example.com/.well-known/openid-configuration",
|
||||
"--quota-group-map", `{"oauth_group_1": ["quota_group_1"], "oauth_group_2": ["quota_group_2"]}`,
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 source with quota group map",
|
||||
IsActive: true,
|
||||
Cfg: &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration",
|
||||
Scopes: []string{},
|
||||
QuotaGroupMap: `{"oauth_group_1": ["quota_group_1"], "oauth_group_2": ["quota_group_2"]}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 9
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--name", "oauth2 source with quota group map removal",
|
||||
"--provider", "openidConnect",
|
||||
"--auto-discover-url", "https://example.com/.well-known/openid-configuration",
|
||||
"--quota-group-map-removal",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 source with quota group map removal",
|
||||
IsActive: true,
|
||||
Cfg: &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration",
|
||||
Scopes: []string{},
|
||||
QuotaGroupMapRemoval: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 10
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--name", "oauth2 source with all quota group fields",
|
||||
"--provider", "openidConnect",
|
||||
"--auto-discover-url", "https://example.com/.well-known/openid-configuration",
|
||||
"--quota-group-claim-name", "quota_groups",
|
||||
"--quota-group-map", `{"developers": ["dev_quota"], "admins": ["admin_quota"]}`,
|
||||
"--quota-group-map-removal",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Name: "oauth2 source with all quota group fields",
|
||||
IsActive: true,
|
||||
Cfg: &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration",
|
||||
Scopes: []string{},
|
||||
QuotaGroupClaimName: "quota_groups",
|
||||
QuotaGroupMap: `{"developers": ["dev_quota"], "admins": ["admin_quota"]}`,
|
||||
QuotaGroupMapRemoval: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for n, c := range cases {
|
||||
|
|
@ -752,92 +658,6 @@ func TestUpdateOauth(t *testing.T) {
|
|||
},
|
||||
errMsg: "--id flag is missing",
|
||||
},
|
||||
// case 23
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--quota-group-claim-name", "quota_groups",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
QuotaGroupClaimName: "quota_groups",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 24
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--quota-group-map", `{"oauth_group_1": ["quota_group_1"], "oauth_group_2": ["quota_group_2"]}`,
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
QuotaGroupMap: `{"oauth_group_1": ["quota_group_1"], "oauth_group_2": ["quota_group_2"]}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 25
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--quota-group-map-removal",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
QuotaGroupMapRemoval: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 26
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "24",
|
||||
"--quota-group-map-removal=false",
|
||||
},
|
||||
id: 24,
|
||||
existingAuthSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
QuotaGroupMapRemoval: true,
|
||||
},
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
QuotaGroupMapRemoval: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 27
|
||||
{
|
||||
args: []string{
|
||||
"oauth-test",
|
||||
"--id", "1",
|
||||
"--quota-group-claim-name", "quota_groups",
|
||||
"--quota-group-map", `{"developers": ["dev_quota"], "admins": ["admin_quota"]}`,
|
||||
"--quota-group-map-removal",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.OAuth2,
|
||||
Cfg: &oauth2.Source{
|
||||
CustomURLMapping: &oauth2.CustomURLMapping{},
|
||||
QuotaGroupClaimName: "quota_groups",
|
||||
QuotaGroupMap: `{"developers": ["dev_quota"], "admins": ["admin_quota"]}`,
|
||||
QuotaGroupMapRemoval: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for n, c := range cases {
|
||||
|
|
|
|||
|
|
@ -1,145 +0,0 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/services/auth/source/pam"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func pamCLIFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Value: "",
|
||||
Usage: "Application Name",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "service-name",
|
||||
Value: "PLAIN",
|
||||
Usage: "PAM service name",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "email-domain",
|
||||
Value: "",
|
||||
Usage: "PAM email domain",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "skip-local-2fa",
|
||||
Usage: "Skip 2FA to log on.",
|
||||
Value: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "active",
|
||||
Usage: "This Authentication Source is Activated.",
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func microcmdAuthAddPAM() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "add-pam",
|
||||
Usage: "Add new PAM authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: newAuthService().addPAM,
|
||||
Flags: pamCLIFlags(),
|
||||
}
|
||||
}
|
||||
|
||||
func microcmdAuthUpdatePAM() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update-pam",
|
||||
Usage: "Update existing PAM authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: newAuthService().updatePAM,
|
||||
Flags: append(pamCLIFlags()[:1], append([]cli.Flag{idFlag()}, pamCLIFlags()[1:]...)...),
|
||||
}
|
||||
}
|
||||
|
||||
func parsePAMConfig(_ context.Context, c *cli.Command) *pam.Source {
|
||||
return &pam.Source{
|
||||
ServiceName: c.String("service-name"),
|
||||
EmailDomain: c.String("email-domain"),
|
||||
SkipLocalTwoFA: c.Bool("skip-local-2fa"),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authService) addPAM(ctx context.Context, c *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.IsSet("name") || len(c.String("name")) == 0 {
|
||||
return errors.New("name must be set")
|
||||
}
|
||||
if !c.IsSet("service-name") || len(c.String("service-name")) == 0 {
|
||||
return errors.New("service-name must be set")
|
||||
}
|
||||
active := true
|
||||
if c.IsSet("active") {
|
||||
active = c.Bool("active")
|
||||
}
|
||||
|
||||
config := parsePAMConfig(ctx, c)
|
||||
|
||||
return a.createAuthSource(ctx, &auth_model.Source{
|
||||
Type: auth_model.PAM,
|
||||
Name: c.String("name"),
|
||||
IsActive: active,
|
||||
Cfg: config,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *authService) updatePAM(ctx context.Context, c *cli.Command) error {
|
||||
if !c.IsSet("id") {
|
||||
return errors.New("--id flag is missing")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err := a.getAuthSource(ctx, c, auth_model.PAM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pamConfig := source.Cfg.(*pam.Source)
|
||||
|
||||
if c.IsSet("name") {
|
||||
source.Name = c.String("name")
|
||||
}
|
||||
|
||||
if c.IsSet("service-name") {
|
||||
pamConfig.ServiceName = c.String("service-name")
|
||||
}
|
||||
|
||||
if c.IsSet("email-domain") {
|
||||
pamConfig.EmailDomain = c.String("email-domain")
|
||||
}
|
||||
|
||||
if c.IsSet("skip-local-2fa") {
|
||||
pamConfig.SkipLocalTwoFA = c.Bool("skip-local-2fa")
|
||||
}
|
||||
|
||||
if c.IsSet("active") {
|
||||
source.IsActive = c.Bool("active")
|
||||
}
|
||||
|
||||
source.Cfg = pamConfig
|
||||
|
||||
return a.updateAuthSource(ctx, source)
|
||||
}
|
||||
|
|
@ -1,293 +0,0 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/models/auth"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/services/auth/source/pam"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func TestPamService(t *testing.T) {
|
||||
// Mock cli functions to do not exit on error
|
||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||
|
||||
// Test cases
|
||||
cases := []struct {
|
||||
args []string
|
||||
source *auth.Source
|
||||
errMsg string
|
||||
}{
|
||||
// case 0
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--name", "Pam Service",
|
||||
"--service-name", "myservice",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Name: "Pam Service",
|
||||
IsActive: true,
|
||||
Cfg: &pam.Source{
|
||||
ServiceName: "myservice",
|
||||
EmailDomain: "",
|
||||
SkipLocalTwoFA: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 1
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--name", "Pam Service",
|
||||
"--service-name", "myservice",
|
||||
"--email-domain", "testdomain.org",
|
||||
"--skip-local-2fa",
|
||||
},
|
||||
source: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Name: "Pam Service",
|
||||
IsActive: true,
|
||||
Cfg: &pam.Source{
|
||||
ServiceName: "myservice",
|
||||
EmailDomain: "testdomain.org",
|
||||
SkipLocalTwoFA: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 2
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--service-name", "myservice",
|
||||
"--email-domain", "testdomain.org",
|
||||
"--skip-local-2fa", "false",
|
||||
"--active", "true",
|
||||
},
|
||||
errMsg: "name must be set",
|
||||
},
|
||||
// case 3
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--name", "Pam Service",
|
||||
"--email-domain", "testdomain.org",
|
||||
"--skip-local-2fa", "false",
|
||||
"--active", "true",
|
||||
},
|
||||
errMsg: "service-name must be set",
|
||||
},
|
||||
}
|
||||
|
||||
for n, c := range cases {
|
||||
// Mock functions.
|
||||
var createdAuthSource *auth.Source
|
||||
service := &authService{
|
||||
initDB: func(context.Context) error {
|
||||
return nil
|
||||
},
|
||||
createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
createdAuthSource = authSource
|
||||
return nil
|
||||
},
|
||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
assert.FailNow(t, "should not call updateAuthSource", "case: %d", n)
|
||||
return nil
|
||||
},
|
||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||
assert.FailNow(t, "should not call getAuthSourceByID", "case: %d", n)
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
// Create a copy of command to test
|
||||
app := cli.Command{}
|
||||
app.Flags = microcmdAuthAddPAM().Flags
|
||||
app.Action = service.addPAM
|
||||
|
||||
// Run it
|
||||
err := app.Run(t.Context(), c.args)
|
||||
if c.errMsg != "" {
|
||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||
} else {
|
||||
require.NoError(t, err, "case %d: should have no errors", n)
|
||||
assert.Equal(t, c.source, createdAuthSource, "case %d: wrong authSource", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePAM(t *testing.T) {
|
||||
// Mock cli functions to do not exit on error
|
||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||
|
||||
// Test cases
|
||||
cases := []struct {
|
||||
args []string
|
||||
id int64
|
||||
existingAuthSource *auth.Source
|
||||
authSource *auth.Source
|
||||
errMsg string
|
||||
}{
|
||||
// case 0
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "23",
|
||||
"--name", "PAM Service",
|
||||
"--service-name", "myservice",
|
||||
},
|
||||
id: 23,
|
||||
existingAuthSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
IsActive: true,
|
||||
Cfg: &pam.Source{},
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Name: "PAM Service",
|
||||
IsActive: true,
|
||||
Cfg: &pam.Source{
|
||||
ServiceName: "myservice",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 1
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Cfg: &pam.Source{},
|
||||
},
|
||||
},
|
||||
// case 2
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
"--name", "pam service",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Name: "pam service",
|
||||
Cfg: &pam.Source{},
|
||||
},
|
||||
},
|
||||
// case 3
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
"--active=false",
|
||||
},
|
||||
existingAuthSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
IsActive: true,
|
||||
Cfg: &pam.Source{},
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
IsActive: false,
|
||||
Cfg: &pam.Source{},
|
||||
},
|
||||
},
|
||||
// case 4
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
"--service-name", "myservice",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Cfg: &pam.Source{
|
||||
ServiceName: "myservice",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 5
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
"--skip-local-2fa=false",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Cfg: &pam.Source{
|
||||
SkipLocalTwoFA: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 6
|
||||
{
|
||||
args: []string{
|
||||
"pam-test",
|
||||
"--id", "1",
|
||||
"--email-domain", "testdomain.org",
|
||||
},
|
||||
authSource: &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Cfg: &pam.Source{
|
||||
EmailDomain: "testdomain.org",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for n, c := range cases {
|
||||
// Mock functions.
|
||||
var updatedAuthSource *auth.Source
|
||||
service := &authService{
|
||||
initDB: func(context.Context) error {
|
||||
return nil
|
||||
},
|
||||
createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
assert.FailNow(t, "should not call createAuthSource", "case: %d", n)
|
||||
return nil
|
||||
},
|
||||
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
|
||||
updatedAuthSource = authSource
|
||||
return nil
|
||||
},
|
||||
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
|
||||
if c.id != 0 {
|
||||
assert.Equal(t, c.id, id, "case %d: wrong id", n)
|
||||
}
|
||||
if c.existingAuthSource != nil {
|
||||
return c.existingAuthSource, nil
|
||||
}
|
||||
return &auth.Source{
|
||||
Type: auth.PAM,
|
||||
Cfg: &pam.Source{},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
// Create a copy of command to test
|
||||
app := cli.Command{}
|
||||
app.Flags = microcmdAuthUpdatePAM().Flags
|
||||
app.Action = service.updatePAM
|
||||
|
||||
// Run it
|
||||
err := app.Run(t.Context(), c.args)
|
||||
if c.errMsg != "" {
|
||||
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
|
||||
} else {
|
||||
require.NoError(t, err, "case %d: should have no errors", n)
|
||||
assert.Equal(t, c.authSource, updatedAuthSource, "case %d: wrong authSource", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,7 +78,6 @@ func microcmdAuthAddSMTP() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "add-smtp",
|
||||
Usage: "Add new SMTP authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: runAddSMTP,
|
||||
Flags: smtpCLIFlags(),
|
||||
}
|
||||
|
|
@ -88,7 +87,6 @@ func microcmdAuthUpdateSMTP() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "update-smtp",
|
||||
Usage: "Update existing SMTP authentication source",
|
||||
Before: noDanglingArgs,
|
||||
Action: runUpdateSMTP,
|
||||
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{idFlag()}, smtpCLIFlags()[1:]...)...),
|
||||
}
|
||||
|
|
@ -7,18 +7,37 @@ import (
|
|||
"context"
|
||||
|
||||
asymkey_model "forgejo.org/models/asymkey"
|
||||
"forgejo.org/modules/graceful"
|
||||
repo_service "forgejo.org/services/repository"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var microcmdRegenKeys = &cli.Command{
|
||||
Name: "keys",
|
||||
Usage: "Regenerate authorized_keys file",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRegenerateKeys,
|
||||
var (
|
||||
microcmdRegenHooks = &cli.Command{
|
||||
Name: "hooks",
|
||||
Usage: "Regenerate git-hooks",
|
||||
Action: runRegenerateHooks,
|
||||
}
|
||||
|
||||
microcmdRegenKeys = &cli.Command{
|
||||
Name: "keys",
|
||||
Usage: "Regenerate authorized_keys file",
|
||||
Action: runRegenerateKeys,
|
||||
}
|
||||
)
|
||||
|
||||
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
|
||||
}
|
||||
|
||||
func runRegenerateKeys(ctx context.Context, c *cli.Command) error {
|
||||
func runRegenerateKeys(ctx context.Context, _ *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ func subcmdUser() *cli.Command {
|
|||
microcmdUserChangePassword(),
|
||||
microcmdUserDelete(),
|
||||
microcmdUserGenerateAccessToken(),
|
||||
microcmdUserCreateAuthorizedIntegration(),
|
||||
microcmdUserMustChangePassword(),
|
||||
microcmdUserResetMFA(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ func microcmdUserChangePassword() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "change-password",
|
||||
Usage: "Change a user's password",
|
||||
Before: noDanglingArgs,
|
||||
Action: runChangePassword,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ func microcmdUserCreate() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a new user in database",
|
||||
Before: noDanglingArgs,
|
||||
Action: runCreateUser,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
@ -205,15 +204,7 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
|
|||
|
||||
// create the access token
|
||||
if accessTokenScope != "" {
|
||||
t := &auth_model.AccessToken{
|
||||
Name: accessTokenName,
|
||||
UID: u.ID,
|
||||
Scope: accessTokenScope,
|
||||
|
||||
// maintain legacy behaviour until new CLI options are added -- token has access to all resources, is not
|
||||
// fine-grained
|
||||
ResourceAllRepos: true,
|
||||
}
|
||||
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
|
||||
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ func microcmdUserDelete() *cli.Command {
|
|||
Usage: "Purge user, all their repositories, organizations and comments",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runDeleteUser,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ func microcmdUserGenerateAccessToken() *cli.Command {
|
|||
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateAccessToken,
|
||||
}
|
||||
}
|
||||
|
|
@ -86,10 +85,6 @@ func runGenerateAccessToken(ctx context.Context, c *cli.Command) error {
|
|||
}
|
||||
t.Scope = accessTokenScope
|
||||
|
||||
// maintain legacy behaviour until new CLI options are added -- token has access to all resources, is not
|
||||
// fine-grained
|
||||
t.ResourceAllRepos = true
|
||||
|
||||
// create the token
|
||||
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -1,225 +0,0 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
auth_model "forgejo.org/models/auth"
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/repo"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/json"
|
||||
"forgejo.org/services/authz"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func microcmdUserCreateAuthorizedIntegration() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "create-authorized-integration",
|
||||
Description: `Creates an authorized integration. Authorized integrations allow Forgejo to
|
||||
receive JWTs from external sources, validate their claims against
|
||||
user-defined rules, and grant access to Forgejo's API on behalf of a user.
|
||||
|
||||
The issuer may be set to "urn:forgejo:authorized-integrations:actions"
|
||||
to support JWTs from the local instance's Forgejo Actions, utilizing the
|
||||
enable-openid-connect flag in a workflow.`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "Username",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "Name of the authorized integration for later identification",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "description",
|
||||
Usage: "Optional description for the authorized integration",
|
||||
},
|
||||
|
||||
// JWT validation:
|
||||
&cli.StringFlag{
|
||||
Name: "issuer",
|
||||
Usage: `JWT issuer ('iss' claim), example: https://forgejo.example.org/api/actions`,
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringMapFlag{
|
||||
Name: "claim-eq",
|
||||
Value: map[string]string{},
|
||||
Usage: `Zero-or-more claim equality checks, formatted as claim=value, example: "actor=someuser"`,
|
||||
},
|
||||
&cli.StringMapFlag{
|
||||
Name: "claim-glob",
|
||||
Value: map[string]string{},
|
||||
Usage: `Zero-or-more claim glob checks, formatted as claim=value, example: "sub=repo:forgejo/*:pull_request"`,
|
||||
},
|
||||
// nested claim support omitted for now -- pretty complex for a CLI
|
||||
|
||||
// Permissions available on successful auth:
|
||||
&cli.StringSliceFlag{
|
||||
Name: "scope",
|
||||
Value: []string{"all"},
|
||||
Usage: `One-or-more scopes to apply to access token, examples: "all", "read:issue", "write:repository"`,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "repo",
|
||||
Value: []string{"all"},
|
||||
Usage: `Zero-or-more specific repositories that can be accessed, or "all" to allow access to all repositories, example: "owner1/repo1"`,
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runCreateAuthorizedIntegration,
|
||||
}
|
||||
}
|
||||
|
||||
func runCreateAuthorizedIntegration(ctx context.Context, c *cli.Command) error {
|
||||
if !c.IsSet("username") {
|
||||
return errors.New("you must provide a username to generate a token for")
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ai := &auth_model.AuthorizedIntegration{
|
||||
UserID: user.ID,
|
||||
Name: c.String("name"),
|
||||
Description: c.String("description"),
|
||||
}
|
||||
|
||||
var rules []auth_model.ClaimRule
|
||||
ai.Issuer = c.String("issuer")
|
||||
for claim, value := range c.StringMap("claim-eq") {
|
||||
rules = append(rules, auth_model.ClaimRule{
|
||||
Claim: claim,
|
||||
Comparison: auth_model.ClaimEqual,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
for claim, value := range c.StringMap("claim-glob") {
|
||||
rules = append(rules, auth_model.ClaimRule{
|
||||
Claim: claim,
|
||||
Comparison: auth_model.ClaimGlob,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
ai.ClaimRules = &auth_model.ClaimRules{Rules: rules}
|
||||
|
||||
scopes := strings.Join(c.StringSlice("scope"), ",")
|
||||
accessTokenScope, err := auth_model.AccessTokenScope(scopes).Normalize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid access token scope provided: %w", err)
|
||||
}
|
||||
ai.Scope = accessTokenScope
|
||||
|
||||
allRepos := false
|
||||
repos := []*repo.Repository{}
|
||||
for _, repoName := range c.StringSlice("repo") {
|
||||
if repoName == "all" {
|
||||
allRepos = true
|
||||
} else {
|
||||
split := strings.Split(repoName, "/")
|
||||
if len(split) != 2 {
|
||||
return fmt.Errorf("invalid repo name: %q", split)
|
||||
}
|
||||
owner := split[0]
|
||||
name := split[1]
|
||||
repo, err := repo.GetRepositoryByOwnerAndName(ctx, owner, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
}
|
||||
ai.ResourceAllRepos = allRepos
|
||||
|
||||
rr := make([]*auth_model.AuthorizedIntegResourceRepo, len(repos))
|
||||
for i := range repos {
|
||||
rr[i] = &auth_model.AuthorizedIntegResourceRepo{RepoID: repos[i].ID}
|
||||
}
|
||||
if err := authz.ValidateAuthorizedIntegration(ai, rr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := auth_model.InsertAuthorizedIntegration(ctx, ai); err != nil {
|
||||
return err
|
||||
}
|
||||
if !allRepos {
|
||||
if err := auth_model.InsertAuthorizedIntegrationResourceRepos(ctx, ai.ID, rr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type ClaimRuleDescription struct {
|
||||
Description string `json:"description"`
|
||||
Claim string `json:"claim"`
|
||||
Comparison auth_model.ClaimComparison `json:"compare"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
output := struct {
|
||||
Message string `json:"message"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Issuer string `json:"issuer"`
|
||||
Audience string `json:"audience"`
|
||||
ClaimRules []ClaimRuleDescription `json:"claim_rules"`
|
||||
}{
|
||||
Message: "Authorized integration was successfully created.",
|
||||
Name: ai.Name,
|
||||
Description: ai.Description,
|
||||
Issuer: ai.Issuer,
|
||||
Audience: ai.Audience,
|
||||
}
|
||||
for _, cr := range ai.ClaimRules.Rules {
|
||||
var description string
|
||||
switch cr.Comparison {
|
||||
case auth_model.ClaimEqual:
|
||||
description = fmt.Sprintf("%q = %q", cr.Claim, cr.Value)
|
||||
case auth_model.ClaimGlob:
|
||||
description = fmt.Sprintf("%q matches %q", cr.Claim, cr.Value)
|
||||
}
|
||||
output.ClaimRules = append(output.ClaimRules, ClaimRuleDescription{
|
||||
Description: description,
|
||||
Claim: cr.Claim,
|
||||
Comparison: cr.Comparison,
|
||||
Value: cr.Value,
|
||||
})
|
||||
}
|
||||
|
||||
raw, err := json.Marshal(output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var indent bytes.Buffer
|
||||
if err := json.Indent(&indent, raw, "", " "); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout.Write(indent.Bytes())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ func microcmdUserList() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List users",
|
||||
Before: noDanglingArgs,
|
||||
Action: runListUsers,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ func microcmdUserResetMFA() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "reset-mfa",
|
||||
Usage: "Remove all two-factor authentication configurations for a user",
|
||||
Before: noDanglingArgs,
|
||||
Action: runResetMFA,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ func cmdCert() *cli.Command {
|
|||
Usage: "Generate self-signed certificate",
|
||||
Description: `Generate a self-signed X.509 certificate for a TLS server.
|
||||
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
|
||||
Before: noDanglingArgs,
|
||||
Action: runCert,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
@ -150,8 +149,8 @@ func runCert(ctx context.Context, c *cli.Command) error {
|
|||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
hosts := strings.SplitSeq(c.String("host"), ",")
|
||||
for h := range hosts {
|
||||
hosts := strings.Split(c.String("host"), ",")
|
||||
for _, h := range hosts {
|
||||
if ip := net.ParseIP(h); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
|
|
|
|||
51
cmd/cmd.go
51
cmd/cmd.go
|
|
@ -1,5 +1,4 @@
|
|||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package cmd provides subcommands to the gitea binary - such as "web" or
|
||||
|
|
@ -13,7 +12,6 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
|
|
@ -42,19 +40,6 @@ func argsSet(c *cli.Command, args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// When a CLI command is intended to be used only with flags and no other arbitrary args, noDanglingArgs will validate
|
||||
// the end-user's usage.
|
||||
func noDanglingArgs(ctx context.Context, c *cli.Command) (context.Context, error) {
|
||||
if c.Args().Len() != 0 {
|
||||
args := c.Args().Slice()
|
||||
if slices.Contains(args, "false") {
|
||||
println("Hint: boolean false must be specified as a single arg, eg. '--restricted=false', not '--restricted false'")
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected arguments: %s", strings.Join(c.Args().Slice(), ", "))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// confirm waits for user input which confirms an action
|
||||
func confirm() (bool, error) {
|
||||
var response string
|
||||
|
|
@ -90,9 +75,26 @@ If this is the intended configuration file complete the [database] section.`, se
|
|||
return nil
|
||||
}
|
||||
|
||||
// installSignals returns a context that's cancelled on the SIGINT and SIGTERM signals or if the passed ctx is cancelled.
|
||||
func installSignals(ctx context.Context) (context.Context, context.CancelFunc) {
|
||||
return signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
go func() {
|
||||
// install notify
|
||||
signalChannel := make(chan os.Signal, 1)
|
||||
|
||||
signal.Notify(
|
||||
signalChannel,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
)
|
||||
select {
|
||||
case <-signalChannel:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
cancel()
|
||||
signal.Reset()
|
||||
}()
|
||||
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) {
|
||||
|
|
@ -133,18 +135,3 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(ctx context.Context,
|
|||
return ctx, nil
|
||||
}
|
||||
}
|
||||
|
||||
func multipleBefore(beforeFuncs ...cli.BeforeFunc) cli.BeforeFunc {
|
||||
return func(ctx context.Context, cli *cli.Command) (context.Context, error) {
|
||||
for _, beforeFunc := range beforeFuncs {
|
||||
bctx, err := beforeFunc(ctx, cli)
|
||||
if err != nil {
|
||||
return bctx, err
|
||||
}
|
||||
if bctx != nil {
|
||||
ctx = bctx
|
||||
}
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_installSignals(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skipf("Windows does not terminate in an awaitable manner")
|
||||
return
|
||||
}
|
||||
|
||||
for _, s := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} {
|
||||
t.Run(fmt.Sprintf("Context is terminated on %s", s), func(t *testing.T) {
|
||||
// Register the signal handler. context.Background() is chosen deliberately,
|
||||
// because unlike t.Context(), we can be sure that it's not cancelled by a
|
||||
// different handler.
|
||||
ctx, cancel := installSignals(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
// Send the signal in the background.
|
||||
go syscall.Kill(syscall.Getpid(), s)
|
||||
|
||||
select {
|
||||
case <-time.Tick(time.Second * 10):
|
||||
t.Fatalf("Context not cancelled via signal after 10 seconds")
|
||||
case <-ctx.Done():
|
||||
t.Logf("Context was cancelled")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -15,9 +15,8 @@ import (
|
|||
"text/tabwriter"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
git_model "forgejo.org/models/git"
|
||||
"forgejo.org/models/gitea_migrations"
|
||||
migrate_base "forgejo.org/models/gitea_migrations/base"
|
||||
"forgejo.org/models/migrations"
|
||||
migrate_base "forgejo.org/models/migrations/base"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/container"
|
||||
|
|
@ -42,7 +41,6 @@ func cmdDoctor() *cli.Command {
|
|||
cmdRecreateTable(),
|
||||
cmdDoctorConvert(),
|
||||
cmdAvatarStripExif(),
|
||||
cmdCleanupCommitStatuses(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -52,7 +50,6 @@ func cmdDoctorCheck() *cli.Command {
|
|||
Name: "check",
|
||||
Usage: "Diagnose and optionally fix problems",
|
||||
Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runDoctorCheck,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
|
|
@ -112,59 +109,10 @@ func cmdAvatarStripExif() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "avatar-strip-exif",
|
||||
Usage: "Strip EXIF metadata from all images in the avatar storage",
|
||||
Before: noDanglingArgs,
|
||||
Action: runAvatarStripExif,
|
||||
}
|
||||
}
|
||||
|
||||
func cmdCleanupCommitStatuses() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "cleanup-commit-status",
|
||||
Usage: "Cleanup extra records in commit_status table",
|
||||
Description: `Forgejo suffered from a bug which caused the creation of more entries in the
|
||||
"commit_status" table than necessary. This operation removes the redundant
|
||||
data caused by the bug. Removing this data is almost always safe.
|
||||
These redundant records can be accessed by users through the API, making it
|
||||
possible, but unlikely, that removing it could have an impact to
|
||||
integrating services (API: /repos/{owner}/{repo}/commits/{ref}/statuses).
|
||||
|
||||
It is safe to run while Forgejo is online.
|
||||
|
||||
On very large Forgejo instances, the performance of operation will improve
|
||||
if the buffer-size option is used with large values. Approximately 130 MB of
|
||||
memory is required for every 100,000 records in the buffer.
|
||||
|
||||
Bug reference: https://codeberg.org/forgejo/forgejo/issues/10671
|
||||
`,
|
||||
|
||||
Before: multipleBefore(noDanglingArgs, PrepareConsoleLoggerLevel(log.INFO)),
|
||||
Action: runCleanupCommitStatus,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Aliases: []string{"V"},
|
||||
Usage: "Show process details",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "Report statistics from the operation but do not modify the database",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "buffer-size",
|
||||
Usage: "Record count per query while iterating records; larger values are typically faster but use more memory",
|
||||
// See IterateByKeyset's documentation for performance notes which led to the choice of the default
|
||||
// buffer size for this operation.
|
||||
Value: 100000,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "delete-chunk-size",
|
||||
Usage: "Number of records to delete per DELETE query",
|
||||
Value: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
|
||||
stdCtx, cancel := installSignals(stdCtx)
|
||||
defer cancel()
|
||||
|
|
@ -209,7 +157,7 @@ func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := gitea_migrations.EnsureUpToDate(engine); err != nil {
|
||||
if err := migrations.EnsureUpToDate(engine); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -372,19 +320,3 @@ func runAvatarStripExif(ctx context.Context, c *cli.Command) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCleanupCommitStatus(ctx context.Context, cli *cli.Command) error {
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bufferSize := cli.Int("buffer-size")
|
||||
deleteChunkSize := cli.Int("delete-chunk-size")
|
||||
dryRun := cli.Bool("dry-run")
|
||||
log.Debug("bufferSize = %d, deleteChunkSize = %d, dryRun = %v", bufferSize, deleteChunkSize, dryRun)
|
||||
|
||||
return git_model.CleanupCommitStatus(ctx, bufferSize, deleteChunkSize, dryRun)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ func cmdDoctorConvert() *cli.Command {
|
|||
Name: "convert",
|
||||
Usage: "Convert the database",
|
||||
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4",
|
||||
Before: noDanglingArgs,
|
||||
Action: runDoctorConvert,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
43
cmd/dump.go
43
cmd/dump.go
|
|
@ -12,7 +12,6 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -84,9 +83,11 @@ func (o outputType) Join() string {
|
|||
}
|
||||
|
||||
func (o *outputType) Set(value string) error {
|
||||
if slices.Contains(o.Enum, value) {
|
||||
o.selected = value
|
||||
return nil
|
||||
for _, enum := range o.Enum {
|
||||
if enum == value {
|
||||
o.selected = value
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("allowed values are %s", o.Join())
|
||||
|
|
@ -112,10 +113,7 @@ func getArchiverByType(outType string) (archives.ArchiverAsync, error) {
|
|||
var archiver archives.ArchiverAsync
|
||||
switch outType {
|
||||
case "zip":
|
||||
archiver = archives.Zip{
|
||||
Compression: 8,
|
||||
SelectiveCompression: false,
|
||||
}
|
||||
archiver = archives.Zip{}
|
||||
case "tar":
|
||||
archiver = archives.Tar{}
|
||||
case "tar.sz":
|
||||
|
|
@ -166,7 +164,6 @@ func cmdDump() *cli.Command {
|
|||
Usage: "Dump Forgejo files and database",
|
||||
Description: `Dump compresses all related files and database into zip file.
|
||||
It can be used for backup and capture Forgejo server image to send to maintainer`,
|
||||
Before: noDanglingArgs,
|
||||
Action: runDump,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
@ -252,8 +249,8 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
|
||||
} else {
|
||||
for _, suffix := range outputTypeEnum.Enum {
|
||||
if before, ok := strings.CutSuffix(fileName, "."+suffix); ok {
|
||||
fileName = before
|
||||
if strings.HasSuffix(fileName, "."+suffix) {
|
||||
fileName = strings.TrimSuffix(fileName, "."+suffix)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -332,12 +329,14 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
go dumpDatabase(ctx, archiveJobs, &wg, verbose)
|
||||
|
||||
if len(setting.CustomConf) > 0 {
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
log.Info("Adding custom configuration file from %s", setting.CustomConf)
|
||||
if err := addFile(archiveJobs, "app.ini", setting.CustomConf, verbose); err != nil {
|
||||
fatal("Failed to include specified app.ini: %v", err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
|
||||
|
|
@ -361,13 +360,15 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
|
||||
log.Info("Skipping attachment data")
|
||||
} else {
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||
return addObject(archiveJobs, object, path.Join("data", "attachments", objPath), verbose)
|
||||
}); err != nil {
|
||||
fatal("Failed to dump attachments: %v", err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
|
||||
|
|
@ -375,13 +376,15 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
} else if !setting.Packages.Enabled {
|
||||
log.Info("Package registry not enabled - skipping")
|
||||
} else {
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
|
||||
return addObject(archiveJobs, object, path.Join("data", "packages", objPath), verbose)
|
||||
}); err != nil {
|
||||
fatal("Failed to dump packages: %v", err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
|
||||
|
|
@ -395,11 +398,13 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
|
|||
log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err)
|
||||
}
|
||||
if isExist {
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := addRecursiveExclude(archiveJobs, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
|
||||
fatal("Failed to include log: %v", err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ func cmdDumpRepository() *cli.Command {
|
|||
Name: "dump-repo",
|
||||
Usage: "Dump the repository from git/github/gitea/gitlab",
|
||||
Description: "This is a command for dumping the repository data.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runDumpRepository,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
@ -143,8 +142,8 @@ func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error {
|
|||
opts.PullRequests = true
|
||||
opts.ReleaseAssets = true
|
||||
} else {
|
||||
units := strings.SplitSeq(ctx.String("units"), ",")
|
||||
for unit := range units {
|
||||
units := strings.Split(ctx.String("units"), ",")
|
||||
for _, unit := range units {
|
||||
switch strings.ToLower(strings.TrimSpace(unit)) {
|
||||
case "":
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -102,11 +102,6 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
|
|||
Value: "",
|
||||
Usage: "version of the runner (not required since v1.21)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "ephemeral",
|
||||
Value: false,
|
||||
Usage: "instruct Forgejo to permanently unregister this runner after it has run one job",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -177,7 +172,6 @@ func RunRegister(ctx context.Context, cli *cli.Command) error {
|
|||
scope := cli.String("scope")
|
||||
name := cli.String("name")
|
||||
version := cli.String("version")
|
||||
ephemeral := cli.Bool("ephemeral")
|
||||
labels, err := getLabels(cli)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -205,7 +199,7 @@ func RunRegister(ctx context.Context, cli *cli.Command) error {
|
|||
return err
|
||||
}
|
||||
|
||||
runner, err := actions_model.RegisterRunner(ctx, owner, repo, secret, labels, name, version, ephemeral)
|
||||
runner, err := actions_model.RegisterRunner(ctx, owner, repo, secret, labels, name, version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while registering runner: %v", err)
|
||||
}
|
||||
|
|
@ -218,7 +212,9 @@ func RunRegister(ctx context.Context, cli *cli.Command) error {
|
|||
|
||||
func RunGenerateSecret(ctx context.Context, cli *cli.Command) error {
|
||||
runner := actions_model.ActionRunner{}
|
||||
runner.GenerateToken()
|
||||
if err := runner.GenerateToken(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprintf(ContextGetStdout(ctx), "%s", runner.Token); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
)
|
||||
|
||||
func CmdF3(ctx context.Context) *cli.Command {
|
||||
ctx = f3_logger.ContextSetLogger(ctx, util.NewF3Logger(nil, log.GetLogger(log.DEFAULT)))
|
||||
return &cli.Command{
|
||||
Name: "f3",
|
||||
Usage: "F3",
|
||||
|
|
@ -37,9 +38,7 @@ func SubcmdF3Mirror(ctx context.Context) *cli.Command {
|
|||
mirrorCmd := f3_cmd.CreateCmdMirror()
|
||||
mirrorCmd.Before = prepareWorkPathAndCustomConf(ctx)
|
||||
f3Action := mirrorCmd.Action
|
||||
mirrorCmd.Action = func(ctx context.Context, cli *cli.Command) error {
|
||||
return runMirror(ctx, cli, f3Action)
|
||||
}
|
||||
mirrorCmd.Action = func(ctx context.Context, cli *cli.Command) error { return runMirror(ctx, cli, f3Action) }
|
||||
return mirrorCmd
|
||||
}
|
||||
|
||||
|
|
@ -68,8 +67,6 @@ func runMirror(ctx context.Context, c *cli.Command, action cli.ActionFunc) error
|
|||
if err := models.Init(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx = f3_logger.ContextSetLogger(ctx, util.NewF3Logger(nil, log.GetLogger(log.DEFAULT)))
|
||||
}
|
||||
|
||||
err := action(ctx, c)
|
||||
|
|
|
|||
|
|
@ -126,9 +126,7 @@ func installSignals(ctx context.Context) (context.Context, context.CancelFunc) {
|
|||
)
|
||||
select {
|
||||
case <-signalChannel:
|
||||
break
|
||||
case <-ctx.Done():
|
||||
break
|
||||
}
|
||||
cancel()
|
||||
signal.Reset()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
func cmdGenerate() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "generate",
|
||||
Usage: "Generate Forgejo's secrets/keys/tokens",
|
||||
Usage: "Generate Gitea's secrets/keys/tokens",
|
||||
Commands: []*cli.Command{
|
||||
subcmdSecret(),
|
||||
},
|
||||
|
|
@ -42,7 +42,6 @@ func microcmdGenerateInternalToken() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "INTERNAL_TOKEN",
|
||||
Usage: "Generate a new INTERNAL_TOKEN",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateInternalToken,
|
||||
}
|
||||
}
|
||||
|
|
@ -52,7 +51,6 @@ func microcmdGenerateLfsJwtSecret() *cli.Command {
|
|||
Name: "JWT_SECRET",
|
||||
Aliases: []string{"LFS_JWT_SECRET"},
|
||||
Usage: "Generate a new JWT_SECRET",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateLfsJwtSecret,
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +59,6 @@ func microcmdGenerateSecretKey() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "SECRET_KEY",
|
||||
Usage: "Generate a new SECRET_KEY",
|
||||
Before: noDanglingArgs,
|
||||
Action: runGenerateSecretKey,
|
||||
}
|
||||
}
|
||||
|
|
@ -94,7 +91,10 @@ func runGenerateLfsJwtSecret(ctx context.Context, c *cli.Command) error {
|
|||
}
|
||||
|
||||
func runGenerateSecretKey(ctx context.Context, c *cli.Command) error {
|
||||
secretKey := generate.NewSecretKey()
|
||||
secretKey, err := generate.NewSecretKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s", secretKey)
|
||||
|
||||
|
|
|
|||
51
cmd/hook.go
51
cmd/hook.go
|
|
@ -237,7 +237,7 @@ Forgejo or set your environment appropriately.`, "")
|
|||
continue
|
||||
}
|
||||
|
||||
fields := bytes.Split(scanner.Bytes(), []byte(" "))
|
||||
fields := bytes.Fields(scanner.Bytes())
|
||||
if len(fields) != 3 {
|
||||
continue
|
||||
}
|
||||
|
|
@ -295,7 +295,35 @@ Forgejo or set your environment appropriately.`, "")
|
|||
|
||||
// runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update
|
||||
func runHookUpdate(ctx context.Context, c *cli.Command) error {
|
||||
return nil
|
||||
// Now if we're an internal don't do anything else
|
||||
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := installSignals(ctx)
|
||||
defer cancel()
|
||||
|
||||
if c.NArg() != 3 {
|
||||
return nil
|
||||
}
|
||||
args := c.Args().Slice()
|
||||
|
||||
// The arguments given to the hook are in order: reference name, old commit ID and new commit ID.
|
||||
refFullName := git.RefName(args[0])
|
||||
newCommitID := args[2]
|
||||
|
||||
// Only process pull references.
|
||||
if !refFullName.IsPull() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Empty new commit ID means deletion.
|
||||
if git.IsEmptyCommitID(newCommitID, nil) {
|
||||
return fail(ctx, fmt.Sprintf("The deletion of %s is skipped as it's an internal reference.", refFullName), "")
|
||||
}
|
||||
|
||||
// If the new comment isn't empty it means modification.
|
||||
return fail(ctx, fmt.Sprintf("The modification of %s is skipped as it's an internal reference.", refFullName), "")
|
||||
}
|
||||
|
||||
func runHookPostReceive(ctx context.Context, c *cli.Command) error {
|
||||
|
|
@ -369,7 +397,7 @@ Forgejo or set your environment appropriately.`, "")
|
|||
continue
|
||||
}
|
||||
|
||||
fields := bytes.Split(scanner.Bytes(), []byte(" "))
|
||||
fields := bytes.Fields(scanner.Bytes())
|
||||
if len(fields) != 3 {
|
||||
continue
|
||||
}
|
||||
|
|
@ -452,17 +480,10 @@ func hookPrintResults(results []private.HookPostReceiveBranchResult) {
|
|||
fmt.Fprintln(os.Stderr, "")
|
||||
if res.Create {
|
||||
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res.Branch)
|
||||
fmt.Fprintf(os.Stderr, " %s\n", res.CreateURL)
|
||||
}
|
||||
if len(res.PullURLS) != 0 {
|
||||
if len(res.PullURLS) >= 2 {
|
||||
fmt.Fprint(os.Stderr, "Visit the existing pull requests:\n")
|
||||
} else {
|
||||
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
|
||||
}
|
||||
for _, url := range res.PullURLS {
|
||||
fmt.Fprintf(os.Stderr, " %s\n", url)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
|
||||
} else {
|
||||
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
|
||||
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "")
|
||||
_ = os.Stderr.Sync()
|
||||
|
|
@ -568,7 +589,7 @@ Forgejo or set your environment appropriately.`, "")
|
|||
hookOptions.RefFullNames = make([]git.RefName, 0, hookBatchSize)
|
||||
|
||||
for {
|
||||
// note: pktLineTypeUnknown means pktLineTypeFlush and pktLineTypeData all allowed
|
||||
// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
|
||||
rs, err = readPktLine(ctx, reader, pktLineTypeUnknown)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
152
cmd/hook_test.go
152
cmd/hook_test.go
|
|
@ -14,9 +14,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"forgejo.org/modules/git"
|
||||
"forgejo.org/modules/json"
|
||||
"forgejo.org/modules/private"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/test"
|
||||
|
||||
|
|
@ -165,132 +162,41 @@ func TestDelayWriter(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestRunHookPrePostReceive(t *testing.T) {
|
||||
// Setup the environment.
|
||||
defer test.MockVariableValue(&setting.InternalToken, "Random")()
|
||||
defer test.MockVariableValue(&setting.InstallLock, true)()
|
||||
defer test.MockVariableValue(&setting.Git.VerbosePush, true)()
|
||||
t.Setenv("SSH_ORIGINAL_COMMAND", "true")
|
||||
func TestRunHookUpdate(t *testing.T) {
|
||||
app := cli.Command{}
|
||||
app.Commands = []*cli.Command{subcmdHookUpdate()}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inputLine string
|
||||
oldCommitID string
|
||||
newCommitID string
|
||||
refFullName string
|
||||
}{
|
||||
{
|
||||
name: "base case",
|
||||
inputLine: "00000000000000000000 00000000000000000001 refs/head/main\n",
|
||||
oldCommitID: "00000000000000000000",
|
||||
newCommitID: "00000000000000000001",
|
||||
refFullName: "refs/head/main",
|
||||
},
|
||||
{
|
||||
name: "nbsp case",
|
||||
inputLine: "00000000000000000000 00000000000000000001 refs/head/ma\u00A0in\n",
|
||||
oldCommitID: "00000000000000000000",
|
||||
newCommitID: "00000000000000000001",
|
||||
refFullName: "refs/head/ma\u00A0in",
|
||||
},
|
||||
}
|
||||
t.Run("Removal of internal reference", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||
defer test.MockVariableValue(&setting.IsProd, false)()
|
||||
finish := captureOutput(t, os.Stderr)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Setup the Stdin.
|
||||
f, err := os.OpenFile(t.TempDir()+"/stdin", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666)
|
||||
require.NoError(t, err)
|
||||
_, err = f.Write([]byte(tt.inputLine))
|
||||
require.NoError(t, err)
|
||||
_, err = f.Seek(0, 0)
|
||||
require.NoError(t, err)
|
||||
defer test.MockVariableValue(os.Stdin, *f)()
|
||||
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
|
||||
out := finish()
|
||||
require.Error(t, err)
|
||||
|
||||
// Setup the server that processes the hooks.
|
||||
var serverError error
|
||||
var hookOpts *private.HookOptions
|
||||
assert.Contains(t, out, "The deletion of refs/pull/1/head is skipped as it's an internal reference.")
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
serverError = err
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
t.Run("Update of internal reference", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
|
||||
defer test.MockVariableValue(&setting.IsProd, false)()
|
||||
finish := captureOutput(t, os.Stderr)
|
||||
|
||||
err = json.Unmarshal(body, &hookOpts)
|
||||
if err != nil {
|
||||
serverError = err
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"})
|
||||
out := finish()
|
||||
require.Error(t, err)
|
||||
|
||||
w.WriteHeader(200)
|
||||
assert.Contains(t, out, "The modification of refs/pull/1/head is skipped as it's an internal reference.")
|
||||
})
|
||||
|
||||
resp := &private.HookPostReceiveResult{}
|
||||
bytes, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
serverError = err
|
||||
return
|
||||
}
|
||||
t.Run("Removal of branch", func(t *testing.T) {
|
||||
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
_, err = w.Write(bytes)
|
||||
if err != nil {
|
||||
serverError = err
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")()
|
||||
|
||||
t.Run("pre-receive", func(t *testing.T) {
|
||||
app := cli.Command{}
|
||||
app.Commands = []*cli.Command{subcmdHookPreReceive()}
|
||||
|
||||
finish := captureOutput(t, os.Stdout)
|
||||
err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"})
|
||||
require.NoError(t, err)
|
||||
out := finish()
|
||||
require.Empty(t, out)
|
||||
|
||||
require.NoError(t, serverError)
|
||||
require.NotNil(t, hookOpts)
|
||||
|
||||
require.Len(t, hookOpts.OldCommitIDs, 1)
|
||||
assert.Equal(t, tt.oldCommitID, hookOpts.OldCommitIDs[0])
|
||||
require.Len(t, hookOpts.NewCommitIDs, 1)
|
||||
assert.Equal(t, tt.newCommitID, hookOpts.NewCommitIDs[0])
|
||||
require.Len(t, hookOpts.RefFullNames, 1)
|
||||
assert.Equal(t, git.RefName(tt.refFullName), hookOpts.RefFullNames[0])
|
||||
})
|
||||
|
||||
// seek stdin back to beginning
|
||||
_, err = f.Seek(0, 0)
|
||||
require.NoError(t, err)
|
||||
// reset state from prev test
|
||||
serverError = nil
|
||||
hookOpts = nil
|
||||
|
||||
t.Run("post-receive", func(t *testing.T) {
|
||||
app := cli.Command{}
|
||||
app.Commands = []*cli.Command{subcmdHookPostReceive()}
|
||||
|
||||
finish := captureOutput(t, os.Stdout)
|
||||
err = app.Run(t.Context(), []string{"./forgejo", "post-receive"})
|
||||
require.NoError(t, err)
|
||||
out := finish()
|
||||
require.Empty(t, out)
|
||||
|
||||
require.NoError(t, serverError)
|
||||
require.NotNil(t, hookOpts)
|
||||
|
||||
require.Len(t, hookOpts.OldCommitIDs, 1)
|
||||
assert.Equal(t, tt.oldCommitID, hookOpts.OldCommitIDs[0])
|
||||
require.Len(t, hookOpts.NewCommitIDs, 1)
|
||||
assert.Equal(t, tt.newCommitID, hookOpts.NewCommitIDs[0])
|
||||
require.Len(t, hookOpts.RefFullNames, 1)
|
||||
assert.Equal(t, git.RefName(tt.refFullName), hookOpts.RefFullNames[0])
|
||||
})
|
||||
})
|
||||
}
|
||||
t.Run("Not enough arguments", func(t *testing.T) {
|
||||
err := app.Run(t.Context(), []string{"./forgejo", "update"})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func cmdKeys() *cli.Command {
|
|||
Name: "keys",
|
||||
Usage: "(internal) Should only be called by SSH server",
|
||||
Description: "Queries the Forgejo database to get the authorized command for a given ssh key fingerprint",
|
||||
Before: multipleBefore(noDanglingArgs, PrepareConsoleLoggerLevel(log.FATAL)),
|
||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||
Action: runKeys,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ func runSendMail(ctx context.Context, c *cli.Command) error {
|
|||
}
|
||||
|
||||
subject := c.String("title")
|
||||
confirmSkipped := c.Bool("force")
|
||||
confirmSkiped := c.Bool("force")
|
||||
body := c.String("content")
|
||||
|
||||
if !confirmSkipped {
|
||||
if !confirmSkiped {
|
||||
if len(body) == 0 {
|
||||
fmt.Print("warning: Content is empty")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ func appGlobalFlags() []cli.Flag {
|
|||
func prepareSubcommandWithConfig(command *cli.Command, globalFlags func() []cli.Flag) {
|
||||
command.Flags = append(globalFlags(), command.Flags...)
|
||||
command.Action = prepareWorkPathAndCustomConf(command.Action)
|
||||
command.HideHelp = true
|
||||
if command.Name != "help" {
|
||||
command.Commands = append(command.Commands, cmdHelp())
|
||||
}
|
||||
|
|
@ -198,6 +199,7 @@ func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmd
|
|||
}
|
||||
app.Flags = append(app.Flags, cli.VersionFlag)
|
||||
app.Flags = append(app.Flags, globalFlags()...)
|
||||
app.HideHelp = true // use our own help action to show helps (with more information like default config)
|
||||
app.Before = PrepareConsoleLoggerLevel(log.INFO)
|
||||
for i := range subCmdWithConfig {
|
||||
prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -63,12 +62,7 @@ func runTestApp(app *cli.Command, args ...string) (runResult, error) {
|
|||
}
|
||||
|
||||
func TestCliCmd(t *testing.T) {
|
||||
path, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defaultWorkPath := filepath.Dir(path)
|
||||
defaultWorkPath := filepath.Dir(setting.AppPath)
|
||||
defaultCustomPath := filepath.Join(defaultWorkPath, "custom")
|
||||
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
|
||||
|
||||
|
|
@ -114,11 +108,6 @@ func TestCliCmd(t *testing.T) {
|
|||
cmd: "./gitea test-cmd --config /tmp/app-other.ini",
|
||||
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"),
|
||||
},
|
||||
{
|
||||
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
|
||||
cmd: "./gitea forgejo-cli --help",
|
||||
exp: "(subcommand help template)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ func subcmdShutdown() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runShutdown,
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +52,6 @@ func subcmdRestart() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runRestart,
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +65,6 @@ func subcmdReloadTemplates() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runReloadTemplates,
|
||||
}
|
||||
}
|
||||
|
|
@ -76,7 +73,6 @@ func subcmdFlushQueues() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "flush-queues",
|
||||
Usage: "Flush queues in the running process",
|
||||
Before: noDanglingArgs,
|
||||
Action: runFlushQueues,
|
||||
Flags: []cli.Flag{
|
||||
&cli.DurationFlag{
|
||||
|
|
@ -99,7 +95,6 @@ func subCmdProcesses() *cli.Command {
|
|||
return &cli.Command{
|
||||
Name: "processes",
|
||||
Usage: "Display running processes within the current process",
|
||||
Before: noDanglingArgs,
|
||||
Action: runProcesses,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ func subcmdLogging() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runPauseLogging,
|
||||
}, {
|
||||
Name: "resume",
|
||||
|
|
@ -87,7 +86,6 @@ func subcmdLogging() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runResumeLogging,
|
||||
}, {
|
||||
Name: "release-and-reopen",
|
||||
|
|
@ -97,7 +95,6 @@ func subcmdLogging() *cli.Command {
|
|||
Name: "debug",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runReleaseReopenLogging,
|
||||
}, {
|
||||
Name: "remove",
|
||||
|
|
@ -159,7 +156,6 @@ func subcmdLogging() *cli.Command {
|
|||
Usage: "Compression level to use",
|
||||
},
|
||||
}...),
|
||||
Before: noDanglingArgs,
|
||||
Action: runAddFileLogger,
|
||||
}, {
|
||||
Name: "conn",
|
||||
|
|
@ -186,7 +182,6 @@ func subcmdLogging() *cli.Command {
|
|||
Usage: "Host address and port to connect to (defaults to :7020)",
|
||||
},
|
||||
}...),
|
||||
Before: noDanglingArgs,
|
||||
Action: runAddConnLogger,
|
||||
},
|
||||
},
|
||||
|
|
@ -202,7 +197,6 @@ func subcmdLogging() *cli.Command {
|
|||
Usage: "Switch off SQL logging",
|
||||
},
|
||||
},
|
||||
Before: noDanglingArgs,
|
||||
Action: runSetLogSQL,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"context"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/models/gitea_migrations"
|
||||
"forgejo.org/models/migrations"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/setting"
|
||||
|
||||
|
|
@ -20,7 +20,6 @@ func cmdMigrate() *cli.Command {
|
|||
Name: "migrate",
|
||||
Usage: "Migrate the database",
|
||||
Description: "This is a command for migrating the database, so that you can run 'forgejo admin user create' before starting the server.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runMigrate,
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +43,7 @@ func runMigrate(stdCtx context.Context, ctx *cli.Command) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gitea_migrations.Migrate(masterEngine)
|
||||
return migrations.Migrate(masterEngine)
|
||||
}); err != nil {
|
||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
actions_model "forgejo.org/models/actions"
|
||||
"forgejo.org/models/db"
|
||||
git_model "forgejo.org/models/git"
|
||||
"forgejo.org/models/gitea_migrations"
|
||||
"forgejo.org/models/migrations"
|
||||
packages_model "forgejo.org/models/packages"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
user_model "forgejo.org/models/user"
|
||||
|
|
@ -32,7 +32,6 @@ func cmdMigrateStorage() *cli.Command {
|
|||
Name: "migrate-storage",
|
||||
Usage: "Migrate the storage",
|
||||
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
|
||||
Before: noDanglingArgs,
|
||||
Action: runMigrateStorage,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
@ -200,7 +199,7 @@ func runMigrateStorage(stdCtx context.Context, ctx *cli.Command) error {
|
|||
log.Info("Configuration file: %s", setting.CustomConf)
|
||||
|
||||
if err := db.InitEngineWithMigration(context.Background(), func(e db.Engine) error {
|
||||
return gitea_migrations.Migrate(e.(*xorm.Engine))
|
||||
return migrations.Migrate(e.(*xorm.Engine))
|
||||
}); err != nil {
|
||||
log.Fatal("Failed to initialize ORM engine: %v", err)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ func cmdRestoreRepository() *cli.Command {
|
|||
Name: "restore-repo",
|
||||
Usage: "Restore the repository from disk",
|
||||
Description: "This is a command for restoring the repository data.",
|
||||
Before: noDanglingArgs,
|
||||
Action: runRestoreRepository,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
|
|||
|
|
@ -290,9 +290,10 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
|||
Op: lfsVerb,
|
||||
UserID: results.UserID,
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Sign and get the complete encoded token as a string using the secret
|
||||
tokenString, err := setting.LFS.SigningKey.JWT(claims)
|
||||
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
|
||||
if err != nil {
|
||||
return fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func cmdWeb() *cli.Command {
|
|||
Usage: "Start the Forgejo web server",
|
||||
Description: `The Forgejo web server is the only thing you need to run,
|
||||
and it takes care of all the other things for you`,
|
||||
Before: multipleBefore(noDanglingArgs, PrepareConsoleLoggerLevel(log.INFO)),
|
||||
Before: PrepareConsoleLoggerLevel(log.INFO),
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
|
@ -164,6 +164,8 @@ func serveInstall(_ context.Context, ctx *cli.Command) error {
|
|||
}
|
||||
|
||||
func serveInstalled(_ context.Context, ctx *cli.Command) error {
|
||||
setting.InitCfgProvider(setting.CustomConf)
|
||||
setting.LoadCommonSettings()
|
||||
setting.MustInstalled()
|
||||
|
||||
showWebStartupMessage("Prepare to run web server")
|
||||
|
|
@ -276,11 +278,8 @@ func setPort(port string) error {
|
|||
|
||||
switch setting.Protocol {
|
||||
case setting.HTTPUnix:
|
||||
break
|
||||
case setting.FCGI:
|
||||
break
|
||||
case setting.FCGIUnix:
|
||||
break
|
||||
default:
|
||||
defaultLocalURL := string(setting.Protocol) + "://"
|
||||
if setting.HTTPAddr == "0.0.0.0" {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue