Merge branch 'main' into bugfix/make-git-user-configurable

This commit is contained in:
stuartbirrell
2024-02-28 19:29:53 +00:00
committed by GitHub
650 changed files with 5834 additions and 2108 deletions
-1
View File
@@ -1,2 +1 @@
open_collective: gitea open_collective: gitea
custom: https://www.bountysource.com/teams/gitea
+6 -1
View File
@@ -19,4 +19,9 @@ jobs:
steps: steps:
- uses: dessant/lock-threads@v5 - uses: dessant/lock-threads@v5
with: with:
issue-inactive-days: 45 issue-inactive-days: 10
issue-comment: |
Automatically locked because of our [CONTRIBUTING guidelines](https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#issue-locking)
pr-inactive-days: 7
pr-comment: |
Automatically locked because of our [CONTRIBUTING guidelines](https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#issue-locking)
+1 -1
View File
@@ -98,7 +98,7 @@ rules:
at-rule-allowed-list: null at-rule-allowed-list: null
at-rule-disallowed-list: null at-rule-disallowed-list: null
at-rule-empty-line-before: null at-rule-empty-line-before: null
at-rule-no-unknown: true at-rule-no-unknown: [true, {ignoreAtRules: [tailwind]}]
at-rule-no-vendor-prefix: true at-rule-no-vendor-prefix: true
at-rule-property-required-list: null at-rule-property-required-list: null
block-no-empty: true block-no-empty: true
+234
View File
@@ -4,6 +4,240 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.com). been added to each release, please refer to the [blog](https://blog.gitea.com).
## [1.21.6](https://github.com/go-gitea/gitea/releases/tag/v1.21.6) - 2024-02-22
* SECURITY
* Fix XSS vulnerabilities (#29336)
* Use general token signing secret (#29205) (#29325)
* ENHANCEMENTS
* Refactor git version functions and check compatibility (#29155) (#29157)
* Improve user experience for outdated comments (#29050) (#29086)
* Hide code links on release page if user cannot read code (#29064) (#29066)
* Wrap contained tags and branches again (#29021) (#29026)
* Fix incorrect button CSS usages (#29015) (#29023)
* Strip trailing newline in markdown code copy (#29019) (#29022)
* Implement some action notifier functions (#29173) (#29308)
* Load outdated comments when (un)resolving conversation on PR timeline (#29203) (#29221)
* BUGFIXES
* Refactor issue template parsing and fix API endpoint (#29069) (#29140)
* Fix swift packages not resolving (#29095) (#29102)
* Remove SSH workaround (#27893) (#29332)
* Only log error when tag sync fails (#29295) (#29327)
* Fix SSPI user creation (#28948) (#29323)
* Improve the `issue_comment` workflow trigger event (#29277) (#29322)
* Discard unread data of `git cat-file` (#29297) (#29310)
* Fix error display when merging PRs (#29288) (#29309)
* Prevent double use of `git cat-file` session. (#29298) (#29301)
* Fix missing link on outgoing new release notifications (#29079) (#29300)
* Fix debian InRelease Acquire-By-Hash newline (#29204) (#29299)
* Always write proc-receive hook for all git versions (#29287) (#29291)
* Do not show delete button when time tracker is disabled (#29257) (#29279)
* Workaround to clean up old reviews on creating a new one (#28554) (#29264)
* Fix bug when the linked account was disactived and list the linked accounts (#29263)
* Do not use lower tag names to find releases/tags (#29261) (#29262)
* Fix missed edit issues event for actions (#29237) (#29251)
* Only delete scheduled workflows when needed (#29091) (#29235)
* Make submit event code work with both jQuery event and native event (#29223) (#29234)
* Fix push to create with capitalize repo name (#29090) (#29206)
* Use ghost user if user was not found (#29161) (#29169)
* Dont load Review if Comment is CommentTypeReviewRequest (#28551) (#29160)
* Refactor parseSignatureFromCommitLine (#29054) (#29108)
* Avoid showing unnecessary JS errors when there are elements with different origin on the page (#29081) (#29089)
* Fix gitea-origin-url with default ports (#29085) (#29088)
* Fix orgmode link resolving (#29024) (#29076)
* Fix Elasticsearh Request Entity Too Large #28117 (#29062) (#29075)
* Do not render empty comments (#29039) (#29049)
* Avoid sending update/delete release notice when it is draft (#29008) (#29025)
* Fix gitea-action user avatar broken on edited menu (#29190) (#29307)
* Disallow merge when required checked are missing (#29143) (#29268)
* Fix incorrect link to swift doc and swift package-registry login command (#29096) (#29103)
* Convert visibility to number (#29226) (#29244)
* DOCS
* Remove outdated docs from some languages (#27530) (#29208)
* Fix typos in the documentation (#29048) (#29056)
* Explained where create issue/PR template (#29035)
## [1.21.5](https://github.com/go-gitea/gitea/releases/tag/v1.21.5) - 2024-01-31
* SECURITY
* Prevent anonymous container access if `RequireSignInView` is enabled (#28877) (#28882)
* Update go dependencies and fix go-git (#28893) (#28934)
* BUGFIXES
* Revert "Speed up loading the dashboard on mysql/mariadb (#28546)" (#29006) (#29007)
* Fix an actions schedule bug (#28942) (#28999)
* Fix update enable_prune even if mirror_interval is not provided (#28905) (#28929)
* Fix uploaded artifacts should be overwritten (#28726) backport v1.21 (#28832)
* Preserve BOM in web editor (#28935) (#28959)
* Strip `/` from relative links (#28932) (#28952)
* Don't remove all mirror repository's releases when mirroring (#28817) (#28939)
* Implement `MigrateRepository` for the actions notifier (#28920) (#28923)
* Respect branch info for relative links (#28909) (#28922)
* Don't reload timeline page when (un)resolving or replying conversation (#28654) (#28917)
* Only migrate the first 255 chars of a Github issue title (#28902) (#28912)
* Fix sort bug on repository issues list (#28897) (#28901)
* Fix `DeleteCollaboration` transaction behaviour (#28886) (#28889)
* Fix schedule not trigger bug because matching full ref name with short ref name (#28874) (#28888)
* Fix migrate storage bug (#28830) (#28867)
* Fix archive creating LFS hooks and breaking pull requests (#28848) (#28851)
* Fix reverting a merge commit failing (#28794) (#28825)
* Upgrade xorm to v1.3.7 to fix a resource leak problem caused by Iterate (#28891) (#28895)
* Fix incorrect PostgreSQL connection string for Unix sockets (#28865) (#28870)
* ENHANCEMENTS
* Make loading animation less aggressive (#28955) (#28956)
* Avoid duplicate JS error messages on UI (#28873) (#28881)
* Bump `@github/relative-time-element` to 4.3.1 (#28819) (#28826)
* MISC
* Warn that `DISABLE_QUERY_AUTH_TOKEN` is false only if it's explicitly defined (#28783) (#28868)
* Remove duplicated checkinit on git module (#28824) (#28831)
## [1.21.4](https://github.com/go-gitea/gitea/releases/tag/v1.21.4) - 2024-01-16
* SECURITY
* Update github.com/cloudflare/circl (#28789) (#28790)
* Require token for GET subscription endpoint (#28765) (#28768)
* BUGFIXES
* Use refname:strip-2 instead of refname:short when syncing tags (#28797) (#28811)
* Fix links in issue card (#28806) (#28807)
* Fix nil pointer panic when exec some gitea cli command (#28791) (#28795)
* Require token for GET subscription endpoint (#28765) (#28778)
* Fix button size in "attached header right" (#28770) (#28774)
* Fix `convert.ToTeams` on empty input (#28426) (#28767)
* Hide code related setting options in repository when code unit is disabled (#28631) (#28749)
* Fix incorrect URL for "Reference in New Issue" (#28716) (#28723)
* Fix panic when parsing empty pgsql host (#28708) (#28709)
* Upgrade xorm to new version which supported update join for all supported databases (#28590) (#28668)
* Fix alpine package files are not rebuilt (#28638) (#28665)
* Avoid cycle-redirecting user/login page (#28636) (#28658)
* Fix empty ref for cron workflow runs (#28640) (#28647)
* Remove unnecessary syncbranchToDB with tests (#28624) (#28629)
* Use known issue IID to generate new PR index number when migrating from GitLab (#28616) (#28618)
* Fix flex container width (#28603) (#28605)
* Fix the scroll behavior for emoji/mention list (#28597) (#28601)
* Fix wrong due date rendering in issue list page (#28588) (#28591)
* Fix `status_check_contexts` matching bug (#28582) (#28589)
* Fix 500 error of searching commits (#28576) (#28579)
* Use information from previous blame parts (#28572) (#28577)
* Update mermaid for 1.21 (#28571)
* Fix 405 method not allowed CORS / OIDC (#28583) (#28586) (#28587) (#28611)
* Fix `GetCommitStatuses` (#28787) (#28804)
* Forbid removing the last admin user (#28337) (#28793)
* Fix schedule tasks bugs (#28691) (#28780)
* Fix issue dependencies (#27736) (#28776)
* Fix system webhooks API bug (#28531) (#28666)
* Fix when private user following user, private user will not be counted in his own view (#28037) (#28792)
* Render code block in activity tab (#28816) (#28818)
* ENHANCEMENTS
* Rework markup link rendering (#26745) (#28803)
* Modernize merge button (#28140) (#28786)
* Speed up loading the dashboard on mysql/mariadb (#28546) (#28784)
* Assign pull request to project during creation (#28227) (#28775)
* Show description as tooltip instead of title for labels (#28754) (#28766)
* Make template `DateTime` show proper tooltip (#28677) (#28683)
* Switch destination directory for apt signing keys (#28639) (#28642)
* Include heap pprof in diagnosis report to help debugging memory leaks (#28596) (#28599)
* DOCS
* Suggest to use Type=simple for systemd service (#28717) (#28722)
* Extend description for ARTIFACT_RETENTION_DAYS (#28626) (#28630)
* MISC
* Add -F to commit search to treat keywords as strings (#28744) (#28748)
* Add download attribute to release attachments (#28739) (#28740)
* Concatenate error in `checkIfPRContentChanged` (#28731) (#28737)
* Improve 1.21 document for Database Preparation (#28643) (#28644)
## [1.21.3](https://github.com/go-gitea/gitea/releases/tag/v1.21.3) - 2023-12-21
* SECURITY
* Update golang.org/x/crypto (#28519)
* API
* chore(api): support ignore password if login source type is LDAP for creating user API (#28491) (#28525)
* Add endpoint for not implemented Docker auth (#28457) (#28462)
* ENHANCEMENTS
* Add option to disable ambiguous unicode characters detection (#28454) (#28499)
* Refactor SSH clone URL generation code (#28421) (#28480)
* Polyfill SubmitEvent for PaleMoon (#28441) (#28478)
* BUGFIXES
* Fix the issue ref rendering for wiki (#28556) (#28559)
* Fix duplicate ID when deleting repo (#28520) (#28528)
* Only check online runner when detecting matching runners in workflows (#28286) (#28512)
* Initalize stroage for orphaned repository doctor (#28487) (#28490)
* Fix possible nil pointer access (#28428) (#28440)
* Don't show unnecessary citation JS error on UI (#28433) (#28437)
* DOCS
* Update actions document about comparsion as Github Actions (#28560) (#28564)
* Fix documents for "custom/public/assets/" (#28465) (#28467)
* MISC
* Fix inperformant query on retrifing review from database. (#28552) (#28562)
* Improve the prompt for "ssh-keygen sign" (#28509) (#28510)
* Update docs for DISABLE_QUERY_AUTH_TOKEN (#28485) (#28488)
* Fix Chinese translation of config cheat sheet[API] (#28472) (#28473)
* Retry SSH key verification with additional CRLF if it failed (#28392) (#28464)
## [1.21.2](https://github.com/go-gitea/gitea/releases/tag/v1.21.2) - 2023-12-12
* SECURITY
* Rebuild with recently released golang version
* Fix missing check (#28406) (#28411)
* Do some missing checks (#28423) (#28432)
* BUGFIXES
* Fix margin in server signed signature verification view (#28379) (#28381)
* Fix object does not exist error when checking citation file (#28314) (#28369)
* Use `filepath` instead of `path` to create SQLite3 database file (#28374) (#28378)
* Fix the runs will not be displayed bug when the main branch have no workflows but other branches have (#28359) (#28365)
* Handle repository.size column being NULL in migration v263 (#28336) (#28363)
* Convert git commit summary to valid UTF8. (#28356) (#28358)
* Fix migration panic due to an empty review comment diff (#28334) (#28362)
* Add `HEAD` support for rpm repo files (#28309) (#28360)
* Fix RPM/Debian signature key creation (#28352) (#28353)
* Keep profile tab when clicking on Language (#28320) (#28331)
* Fix missing issue search index update when changing status (#28325) (#28330)
* Fix wrong link in `protect_branch_name_pattern_desc` (#28313) (#28315)
* Read `previous` info from git blame (#28306) (#28310)
* Ignore "non-existing" errors when getDirectorySize calculates the size (#28276) (#28285)
* Use appSubUrl for OAuth2 callback URL tip (#28266) (#28275)
* Meilisearch: require all query terms to be matched (#28293) (#28296)
* Fix required error for token name (#28267) (#28284)
* Fix issue will be detected as pull request when checking `First-time contributor` (#28237) (#28271)
* Use full width for project boards (#28225) (#28245)
* Increase "version" when update the setting value to a same value as before (#28243) (#28244)
* Also sync DB branches on push if necessary (#28361) (#28403)
* Make gogit Repository.GetBranchNames consistent (#28348) (#28386)
* Recover from panic in cron task (#28409) (#28425)
* Deprecate query string auth tokens (#28390) (#28430)
* ENHANCEMENTS
* Improve doctor cli behavior (#28422) (#28424)
* Fix margin in server signed signature verification view (#28379) (#28381)
* Refactor template empty checks (#28351) (#28354)
* Read `previous` info from git blame (#28306) (#28310)
* Use full width for project boards (#28225) (#28245)
* Enable system users search via the API (#28013) (#28018)
## [1.21.1](https://github.com/go-gitea/gitea/releases/tag/v1.21.1) - 2023-11-26
* SECURITY
* Fix comment permissions (#28213) (#28216)
* BUGFIXES
* Fix delete-orphaned-repos (#28200) (#28202)
* Make CORS work for oauth2 handlers (#28184) (#28185)
* Fix missing buttons (#28179) (#28181)
* Fix no ActionTaskOutput table waring (#28149) (#28152)
* Fix empty action run title (#28113) (#28148)
* Use "is-loading" to avoid duplicate form submit for code comment (#28143) (#28147)
* Fix Matrix and MSTeams nil dereference (#28089) (#28105)
* Fix incorrect pgsql conn builder behavior (#28085) (#28098)
* Fix system config cache expiration timing (#28072) (#28090)
* Restricted users only see repos in orgs which their team was assigned to (#28025) (#28051)
* API
* Fix permissions for Token DELETE endpoint to match GET and POST (#27610) (#28099)
* ENHANCEMENTS
* Do not display search box when there's no packages yet (#28146) (#28159)
* Add missing `packages.cleanup.success` (#28129) (#28132)
* DOCS
* Docs: Replace deprecated IS_TLS_ENABLED mailer setting in email setup (#28205) (#28208)
* Fix the description about the default setting for action in quick start document (#28160) (#28168)
* Add guide page to actions when there's no workflows (#28145) (#28153)
* MISC
* Use full width for PR comparison (#28182) (#28186)
## [1.21.0](https://github.com/go-gitea/gitea/releases/tag/v1.21.0) - 2023-11-14 ## [1.21.0](https://github.com/go-gitea/gitea/releases/tag/v1.21.0) - 2023-11-14
* BREAKING * BREAKING
+8
View File
@@ -8,6 +8,7 @@
- [How to report issues](#how-to-report-issues) - [How to report issues](#how-to-report-issues)
- [Types of issues](#types-of-issues) - [Types of issues](#types-of-issues)
- [Discuss your design before the implementation](#discuss-your-design-before-the-implementation) - [Discuss your design before the implementation](#discuss-your-design-before-the-implementation)
- [Issue locking](#issue-locking)
- [Building Gitea](#building-gitea) - [Building Gitea](#building-gitea)
- [Dependencies](#dependencies) - [Dependencies](#dependencies)
- [Backend](#backend) - [Backend](#backend)
@@ -103,6 +104,13 @@ the goals for the project and tools.
Pull requests should not be the place for architecture discussions. Pull requests should not be the place for architecture discussions.
### Issue locking
Commenting on closed or merged issues/PRs is strongly discouraged.
Such comments will likely be overlooked as some maintainers may not view notifications on closed issues, thinking that the item is resolved.
As such, commenting on closed/merged issues/PRs may be disabled prior to the scheduled auto-locking if a discussion starts or if unrelated comments are posted.
If further discussion is needed, we encourage you to open a new issue instead and we recommend linking to the issue/PR in question for context.
## Building Gitea ## Building Gitea
See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea). See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea).
+1
View File
@@ -59,3 +59,4 @@ Rui Chen <rui@chenrui.dev> (@chenrui333)
Nanguan Lin <nanguanlin6@gmail.com> (@lng2020) Nanguan Lin <nanguanlin6@gmail.com> (@lng2020)
kerwin612 <kerwin612@qq.com> (@kerwin612) kerwin612 <kerwin612@qq.com> (@kerwin612)
Gary Wang <git@blumia.net> (@BLumia) Gary Wang <git@blumia.net> (@BLumia)
Tim-Niclas Oelschläger <zokki.softwareschmiede@gmail.com> (@zokkis)
+2 -2
View File
@@ -119,7 +119,7 @@ GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/m
FOMANTIC_WORK_DIR := web_src/fomantic FOMANTIC_WORK_DIR := web_src/fomantic
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
WEBPACK_CONFIGS := webpack.config.js WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack
@@ -969,7 +969,7 @@ generate-gitignore:
.PHONY: generate-images .PHONY: generate-images
generate-images: | node_modules generate-images: | node_modules
npm install --no-save --no-package-lock fabric@5 imagemin-zopfli@7 npm install --no-save fabric@6.0.0-beta19 imagemin-zopfli@7
node build/generate-images.js $(TAGS) node build/generate-images.js $(TAGS)
.PHONY: generate-manpage .PHONY: generate-manpage
-3
View File
@@ -45,9 +45,6 @@
<a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs"> <a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs">
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main"> <img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main">
</a> </a>
<a href="https://app.bountysource.com/teams/gitea" title="Bountysource">
<img src="https://img.shields.io/bountysource/team/gitea/activity">
</a>
</p> </p>
<p align="center"> <p align="center">
-3
View File
@@ -45,9 +45,6 @@
<a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs"> <a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs">
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main"> <img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main">
</a> </a>
<a href="https://app.bountysource.com/teams/gitea" title="Bountysource">
<img src="https://img.shields.io/bountysource/team/gitea/activity">
</a>
</p> </p>
<p align="center"> <p align="center">
+11 -18
View File
@@ -1,20 +1,13 @@
#!/usr/bin/env node #!/usr/bin/env node
import imageminZopfli from 'imagemin-zopfli'; import imageminZopfli from 'imagemin-zopfli';
import {optimize} from 'svgo'; import {optimize} from 'svgo';
import {fabric} from 'fabric'; import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node';
import {readFile, writeFile} from 'node:fs/promises'; import {readFile, writeFile} from 'node:fs/promises';
import {argv, exit} from 'node:process';
function exit(err) { function doExit(err) {
if (err) console.error(err); if (err) console.error(err);
process.exit(err ? 1 : 0); exit(err ? 1 : 0);
}
function loadSvg(svg) {
return new Promise((resolve) => {
fabric.loadSVGFromString(svg, (objects, options) => {
resolve({objects, options});
});
});
} }
async function generate(svg, path, {size, bg}) { async function generate(svg, path, {size, bg}) {
@@ -35,14 +28,14 @@ async function generate(svg, path, {size, bg}) {
return; return;
} }
const {objects, options} = await loadSvg(svg); const {objects, options} = await loadSVGFromString(svg);
const canvas = new fabric.Canvas(); const canvas = new Canvas();
canvas.setDimensions({width: size, height: size}); canvas.setDimensions({width: size, height: size});
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1); ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1);
if (bg) { if (bg) {
canvas.add(new fabric.Rect({ canvas.add(new Rect({
left: 0, left: 0,
top: 0, top: 0,
height: size * (1 / (size / options.height)), height: size * (1 / (size / options.height)),
@@ -51,7 +44,7 @@ async function generate(svg, path, {size, bg}) {
})); }));
} }
canvas.add(fabric.util.groupSVGElements(objects, options)); canvas.add(util.groupSVGElements(objects, options));
canvas.renderAll(); canvas.renderAll();
let png = Buffer.from([]); let png = Buffer.from([]);
@@ -64,7 +57,7 @@ async function generate(svg, path, {size, bg}) {
} }
async function main() { async function main() {
const gitea = process.argv.slice(2).includes('gitea'); const gitea = argv.slice(2).includes('gitea');
const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8'); const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8');
const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8'); const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8');
@@ -80,7 +73,7 @@ async function main() {
} }
try { try {
exit(await main()); doExit(await main());
} catch (err) { } catch (err) {
exit(err); doExit(err);
} }
+5 -4
View File
@@ -4,15 +4,16 @@ import {optimize} from 'svgo';
import {parse} from 'node:path'; import {parse} from 'node:path';
import {readFile, writeFile, mkdir} from 'node:fs/promises'; import {readFile, writeFile, mkdir} from 'node:fs/promises';
import {fileURLToPath} from 'node:url'; import {fileURLToPath} from 'node:url';
import {exit} from 'node:process';
const glob = (pattern) => fastGlob.sync(pattern, { const glob = (pattern) => fastGlob.sync(pattern, {
cwd: fileURLToPath(new URL('..', import.meta.url)), cwd: fileURLToPath(new URL('..', import.meta.url)),
absolute: true, absolute: true,
}); });
function exit(err) { function doExit(err) {
if (err) console.error(err); if (err) console.error(err);
process.exit(err ? 1 : 0); exit(err ? 1 : 0);
} }
async function processFile(file, {prefix, fullName} = {}) { async function processFile(file, {prefix, fullName} = {}) {
@@ -64,7 +65,7 @@ async function main() {
} }
try { try {
exit(await main()); doExit(await main());
} catch (err) { } catch (err) {
exit(err); doExit(err);
} }
+4 -4
View File
@@ -10,8 +10,8 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/auth/password" pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@@ -123,10 +123,10 @@ func runCreateUser(c *cli.Context) error {
changePassword = c.Bool("must-change-password") changePassword = c.Bool("must-change-password")
} }
restricted := util.OptionalBoolNone restricted := optional.None[bool]()
if c.IsSet("restricted") { if c.IsSet("restricted") {
restricted = util.OptionalBoolOf(c.Bool("restricted")) restricted = optional.Some(c.Bool("restricted"))
} }
// default user visibility in app.ini // default user visibility in app.ini
@@ -142,7 +142,7 @@ func runCreateUser(c *cli.Context) error {
} }
overwriteDefault := &user_model.CreateUserOverwriteOptions{ overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: util.OptionalBoolTrue, IsActive: optional.Some(true),
IsRestricted: restricted, IsRestricted: restricted,
} }
+7
View File
@@ -412,6 +412,10 @@ USER = root
;; ;;
;; Whether execute database models migrations automatically ;; Whether execute database models migrations automatically
;AUTO_MIGRATION = true ;AUTO_MIGRATION = true
;;
;; Threshold value (in seconds) beyond which query execution time is logged as a warning in the xorm logger
;;
;SLOW_QUERY_THRESHOLD = 5s
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1470,6 +1474,9 @@ LEVEL = Info
;; ;;
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled ;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
;DEFAULT_EMAIL_NOTIFICATIONS = enabled ;DEFAULT_EMAIL_NOTIFICATIONS = enabled
;; Disabled features for users, could be "deletion", more features can be disabled in future
;; - deletion: a user cannot delete their own account
;USER_DISABLED_FEATURES =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -92,7 +92,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/ mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/ mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/gitea-repositories/ mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql # mysql
@@ -111,6 +111,8 @@ With Gitea running, and from the directory Gitea's binary is located, execute: `
This ensures that application and configuration file paths in repository Git Hooks are consistent and applicable to the current installation. If these paths are not updated, repository `push` actions will fail. This ensures that application and configuration file paths in repository Git Hooks are consistent and applicable to the current installation. If these paths are not updated, repository `push` actions will fail.
If you still have issues, consider running `./gitea doctor check` to inspect possible errors (or run with `--fix`).
### Using Docker (`restore`) ### Using Docker (`restore`)
There is also no support for a recovery command in a Docker-based gitea instance. The restore process contains the same steps as described in the previous section but with different paths. There is also no support for a recovery command in a Docker-based gitea instance. The restore process contains the same steps as described in the previous section but with different paths.
@@ -458,6 +458,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `MAX_IDLE_CONNS` **2**: Max idle database connections on connection pool, default is 2 - this will be capped to `MAX_OPEN_CONNS`. - `MAX_IDLE_CONNS` **2**: Max idle database connections on connection pool, default is 2 - this will be capped to `MAX_OPEN_CONNS`.
- `CONN_MAX_LIFETIME` **0 or 3s**: Sets the maximum amount of time a DB connection may be reused - default is 0, meaning there is no limit (except on MySQL where it is 3s - see #6804 & #7071). - `CONN_MAX_LIFETIME` **0 or 3s**: Sets the maximum amount of time a DB connection may be reused - default is 0, meaning there is no limit (except on MySQL where it is 3s - see #6804 & #7071).
- `AUTO_MIGRATION` **true**: Whether execute database models migrations automatically. - `AUTO_MIGRATION` **true**: Whether execute database models migrations automatically.
- `SLOW_QUERY_THRESHOLD` **5s**: Threshold value in seconds beyond which query execution time is logged as a warning in the xorm logger.
[^1]: It may be necessary to specify a hostport even when listening on a unix socket, as the port is part of the socket name. see [#24552](https://github.com/go-gitea/gitea/issues/24552#issuecomment-1681649367) for additional details. [^1]: It may be necessary to specify a hostport even when listening on a unix socket, as the port is part of the socket name. see [#24552](https://github.com/go-gitea/gitea/issues/24552#issuecomment-1681649367) for additional details.
@@ -517,6 +518,8 @@ And the following unique queues:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations. - `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion` and more features can be added in future.
- `deletion`: User cannot delete their own account.
## Security (`security`) ## Security (`security`)
@@ -497,6 +497,8 @@ Gitea 创建以下非唯一队列:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**:用户电子邮件通知的默认配置(用户可配置)。选项:enabled、onmention、disabled - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**:用户电子邮件通知的默认配置(用户可配置)。选项:enabled、onmention、disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。 - `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。
- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion` 未来可以增加更多设置。
- `deletion`: 用户不能通过界面或者API删除他自己。
## 安全性 (`security`) ## 安全性 (`security`)
@@ -266,7 +266,7 @@ the messages. Here's a list of some of them:
| `AppDomain` | - | Any | Gitea's host name | | `AppDomain` | - | Any | Gitea's host name |
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed | | `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
| `Str2html` | string | Body only | Sanitizes text by removing any HTML tags from it. | | `Str2html` | string | Body only | Sanitizes text by removing any HTML tags from it. |
| `Safe` | string | Body only | Takes the input as HTML; can be used for `.ReviewComments.RenderedContent`. | | `SafeHTML` | string | Body only | Takes the input as HTML; can be used for `.ReviewComments.RenderedContent`. |
These are _functions_, not metadata, so they have to be used: These are _functions_, not metadata, so they have to be used:
@@ -242,14 +242,14 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
模板系统包含一些函数,可用于进一步处理和格式化消息。以下是其中一些函数的列表: 模板系统包含一些函数,可用于进一步处理和格式化消息。以下是其中一些函数的列表:
| 函数名 | 参数 | 可用于 | 用法 | | 函数名 | 参数 | 可用于 | 用法 |
| ----------------- | ----------- | ------------ | --------------------------------------------------------------------------------- | |------------------| ----------- | ------------ | --------------------------------------------------------------------------------- |
| `AppUrl` | - | 任何地方 | Gitea 的 URL | | `AppUrl` | - | 任何地方 | Gitea 的 URL |
| `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" | | `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" |
| `AppDomain` | - | 任何地方 | Gitea 的主机名 | | `AppDomain` | - | 任何地方 | Gitea 的主机名 |
| `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 | | `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 |
| `Str2html` | string | 仅正文部分 | 通过删除其中的 HTML 标签对文本进行清理 | | `Str2html` | string | 仅正文部分 | 通过删除其中的 HTML 标签对文本进行清理 |
| `Safe` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于 `.ReviewComments.RenderedContent` 等字段 | | `SafeHTML` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于 `.ReviewComments.RenderedContent` 等字段 |
这些都是 _函数_,而不是元数据,因此必须按以下方式使用: 这些都是 _函数_,而不是元数据,因此必须按以下方式使用:
@@ -101,6 +101,10 @@ i.e. `services/user`, `models/repository`.
Since there are some packages which use the same package name, it is possible that you find packages like `modules/user`, `models/user`, and `services/user`. When these packages are imported in one Go file, it's difficult to know which package we are using and if it's a variable name or an import name. So, we always recommend to use import aliases. To differ from package variables which are commonly in camelCase, just use **snake_case** for import aliases. Since there are some packages which use the same package name, it is possible that you find packages like `modules/user`, `models/user`, and `services/user`. When these packages are imported in one Go file, it's difficult to know which package we are using and if it's a variable name or an import name. So, we always recommend to use import aliases. To differ from package variables which are commonly in camelCase, just use **snake_case** for import aliases.
i.e. `import user_service "code.gitea.io/gitea/services/user"` i.e. `import user_service "code.gitea.io/gitea/services/user"`
### Implementing `io.Closer`
If a type implements `io.Closer`, calling `Close` multiple times must not fail or `panic` but return an error or `nil`.
### Important Gotchas ### Important Gotchas
- Never write `x.Update(exemplar)` without an explicit `WHERE` clause: - Never write `x.Update(exemplar)` without an explicit `WHERE` clause:
+7 -8
View File
@@ -45,25 +45,24 @@ It is technically possible to implement, but we need to discuss whether it is ne
## Where will the runner download scripts when using actions such as `actions/checkout@v4`? ## Where will the runner download scripts when using actions such as `actions/checkout@v4`?
You may be aware that there are tens of thousands of [marketplace actions](https://github.com/marketplace?type=actions) in GitHub. There are tens of thousands of [actions scripts](https://github.com/marketplace?type=actions) in GitHub, and when you write `uses: actions/checkout@v4`, it downloads the scripts from [github.com/actions/checkout](http://github.com/actions/checkout) by default.
However, when you write `uses: actions/checkout@v4`, it actually downloads the scripts from [gitea.com/actions/checkout](http://gitea.com/actions/checkout) by default (not GitHub). But what if you want to use actions from other places such as gitea.com instead of GitHub?
This is a mirror of [github.com/actions/checkout](http://github.com/actions/checkout), but it's impossible to mirror all of them.
That's why you may encounter failures when trying to use some actions that haven't been mirrored.
The good news is that you can specify the URL prefix to use actions from anywhere. The good news is that you can specify the URL prefix to use actions from anywhere.
This is an extra syntax in Gitea Actions. This is an extra syntax in Gitea Actions.
For example: For example:
- `uses: https://github.com/xxx/xxx@xxx`
- `uses: https://gitea.com/xxx/xxx@xxx` - `uses: https://gitea.com/xxx/xxx@xxx`
- `uses: https://github.com/xxx/xxx@xxx`
- `uses: http://your_gitea_instance.com/xxx@xxx` - `uses: http://your_gitea_instance.com/xxx@xxx`
Be careful, the `https://` or `http://` prefix is necessary! Be careful, the `https://` or `http://` prefix is necessary!
Alternatively, if you want your runners to download actions from GitHub or your own Gitea instance by default, you can configure it by setting `[actions].DEFAULT_ACTIONS_URL`. This is one of the differences from GitHub Actions which supports actions scripts only from GitHub.
See [Configuration Cheat Sheet](administration/config-cheat-sheet.md#actions-actions). But it should allow users much more flexibility in how they run Actions.
This is one of the differences from GitHub Actions, but it should allow users much more flexibility in how they run Actions. Alternatively, if you want your runners to download actions from your own Gitea instance by default, you can configure it by setting `[actions].DEFAULT_ACTIONS_URL`.
See [Configuration Cheat Sheet](administration/config-cheat-sheet.md#actions-actions).
## How to limit the permission of the runners? ## How to limit the permission of the runners?
+8 -8
View File
@@ -45,25 +45,25 @@ DEFAULT_REPO_UNITS = ...,repo.actions
## 使用`actions/checkout@v4`等Actions时,Job容器会从何处下载脚本? ## 使用`actions/checkout@v4`等Actions时,Job容器会从何处下载脚本?
您可能知道GitHub上有成千上万个[Actions市场](https://github.com/marketplace?type=actions)。 GitHub 上有成千上万个 [Actions 脚本](https://github.com/marketplace?type=actions)。
然而,当您编写`uses: actions/checkout@v4`时,它实际上默认从[gitea.com/actions/checkout](http://gitea.com/actions/checkout)下载脚本(而不是从GitHub下载) 当您编写 `uses: actions/checkout@v4` 时,它默认会从 [github.com/actions/checkout](https://github.com/actions/checkout) 下载脚本。
这是[github.com/actions/checkout](http://github.com/actions/checkout)的镜像,但无法将它们全部镜像。 那如果您想使用一些托管在其它平台上的脚本呢,比如在 gitea.com 上的?
这就是为什么在尝试使用尚未镜像的某些Actions时可能会遇到失败的原因。
好消息是,您可以指定要从任何位置使用Actions的URL前缀。 好消息是,您可以指定要从任何位置使用Actions的URL前缀。
这是Gitea Actions中的额外语法。 这是Gitea Actions中的额外语法。
例如: 例如:
- `uses: https://github.com/xxx/xxx@xxx`
- `uses: https://gitea.com/xxx/xxx@xxx` - `uses: https://gitea.com/xxx/xxx@xxx`
- `uses: https://github.com/xxx/xxx@xxx`
- `uses: http://your_gitea_instance.com/xxx@xxx` - `uses: http://your_gitea_instance.com/xxx@xxx`
注意,`https://``http://`前缀是必需的! 注意,`https://``http://`前缀是必需的!
另外,如果您希望您的Runner默认从GitHub或您自己的Gitea实例下载Actions,可以通过设置 `[actions].DEFAULT_ACTIONS_URL`进行配置 这是与 GitHub Actions 的一个区别,GitHub Actions 只允许使用托管在 GitHub 上的 actions 脚本
参见[配置速查表](administration/config-cheat-sheet.md#actions-actions) 但用户理应拥有权利去灵活决定如何运行 Actions
这是与GitHub Actions的一个区别,但它应该允许用户以更灵活的方式运行Actions 另外,如果您希望您的 Runner 默认从您自己的 Gitea 实例下载 Actions,可以通过设置 `[actions].DEFAULT_ACTIONS_URL`进行配置
参见[配置速查表](administration/config-cheat-sheet.md#actions-actions)。
## 如何限制Runner的权限? ## 如何限制Runner的权限?
+37
View File
@@ -0,0 +1,37 @@
---
date: "2023-02-25T00:00:00+00:00"
title: "Badge"
slug: "badge"
sidebar_position: 11
toc: false
draft: false
aliases:
- /en-us/badge
menu:
sidebar:
parent: "usage"
name: "Badge"
sidebar_position: 11
identifier: "Badge"
---
# Badge
Gitea has its builtin Badge system which allows you to display the status of your repository in other places. You can use the following badges:
## Workflow Badge
The Gitea Actions workflow badge is a badge that shows the status of the latest workflow run.
It is designed to be compatible with [GitHub Actions workflow badge](https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/adding-a-workflow-status-badge).
You can use the following URL to get the badge:
```
https://your-gitea-instance.com/{owner}/{repo}/actions/workflows/{workflow_file}?branch={branch}&event={event}
```
- `{owner}`: The owner of the repository.
- `{repo}`: The name of the repository.
- `{workflow_file}`: The name of the workflow file.
- `{branch}`: Optional. The branch of the workflow. Default to your repository's default branch.
- `{event}`: Optional. The event of the workflow. Default to none.
+1 -1
View File
@@ -78,7 +78,6 @@ require (
github.com/mholt/archiver/v3 v3.5.1 github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.26 github.com/microcosm-cc/bluemonday v1.0.26
github.com/minio/minio-go/v7 v7.0.66 github.com/minio/minio-go/v7 v7.0.66
github.com/minio/sha256-simd v1.0.1
github.com/msteinert/pam v1.2.0 github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.52 github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.7.0 github.com/niklasfasching/go-org v1.7.0
@@ -230,6 +229,7 @@ require (
github.com/mholt/acmez v1.2.0 // indirect github.com/mholt/acmez v1.2.0 // indirect
github.com/miekg/dns v1.1.58 // indirect github.com/miekg/dns v1.1.58 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
+17
View File
@@ -339,6 +339,23 @@ func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error)
return run, nil return run, nil
} }
func GetWorkflowLatestRun(ctx context.Context, repoID int64, workflowFile, branch, event string) (*ActionRun, error) {
var run ActionRun
q := db.GetEngine(ctx).Where("repo_id=?", repoID).
And("ref = ?", branch).
And("workflow_id = ?", workflowFile)
if event != "" {
q.And("event = ?", event)
}
has, err := q.Desc("id").Get(&run)
if err != nil {
return nil, err
} else if !has {
return nil, util.NewNotExistErrorf("run with repo_id %d, ref %s, workflow_id %s", repoID, branch, workflowFile)
}
return &run, nil
}
// UpdateRun updates a run. // UpdateRun updates a run.
// It requires the inputted run has Version set. // It requires the inputted run has Version set.
// It will return error if the version is not matched (it means the run has been changed after loaded). // It will return error if the version is not matched (it means the run has been changed after loaded).
+1 -1
View File
@@ -6,6 +6,7 @@ package auth
import ( import (
"context" "context"
"crypto/md5" "crypto/md5"
"crypto/sha256"
"crypto/subtle" "crypto/subtle"
"encoding/base32" "encoding/base32"
"encoding/base64" "encoding/base64"
@@ -18,7 +19,6 @@ import (
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/minio/sha256-simd"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
) )
+31
View File
@@ -11,10 +11,13 @@ import (
"io" "io"
"reflect" "reflect"
"strings" "strings"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"xorm.io/xorm" "xorm.io/xorm"
"xorm.io/xorm/contexts"
"xorm.io/xorm/names" "xorm.io/xorm/names"
"xorm.io/xorm/schemas" "xorm.io/xorm/schemas"
@@ -143,6 +146,13 @@ func InitEngine(ctx context.Context) error {
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime) xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
xormEngine.SetDefaultContext(ctx) xormEngine.SetDefaultContext(ctx)
if setting.Database.SlowQueryThreshold > 0 {
xormEngine.AddHook(&SlowQueryHook{
Threshold: setting.Database.SlowQueryThreshold,
Logger: log.GetLogger("xorm"),
})
}
SetDefaultEngine(ctx, xormEngine) SetDefaultEngine(ctx, xormEngine)
return nil return nil
} }
@@ -298,3 +308,24 @@ func SetLogSQL(ctx context.Context, on bool) {
sess.Engine().ShowSQL(on) sess.Engine().ShowSQL(on)
} }
} }
type SlowQueryHook struct {
Threshold time.Duration
Logger log.Logger
}
var _ contexts.Hook = &SlowQueryHook{}
func (SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
return c.Ctx, nil
}
func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error {
if c.ExecuteTime >= h.Threshold {
// 8 is the amount of skips passed to runtime.Caller, so that in the log the correct function
// is being displayed (the function that ultimately wants to execute the query in the code)
// instead of the function of the slow query hook being called.
h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime)
}
return nil
}
+24
View File
@@ -135,3 +135,27 @@
user_id: 31 user_id: 31
repo_id: 28 repo_id: 28
mode: 4 mode: 4
-
id: 24
user_id: 38
repo_id: 60
mode: 2
-
id: 25
user_id: 38
repo_id: 61
mode: 1
-
id: 26
user_id: 39
repo_id: 61
mode: 1
-
id: 27
user_id: 40
repo_id: 61
mode: 4
+6
View File
@@ -45,3 +45,9 @@
repo_id: 22 repo_id: 22
user_id: 18 user_id: 18
mode: 2 # write mode: 2 # write
-
id: 9
repo_id: 60
user_id: 38
mode: 2 # write
+24
View File
@@ -293,3 +293,27 @@
lower_email: user37@example.com lower_email: user37@example.com
is_activated: true is_activated: true
is_primary: true is_primary: true
-
id: 38
uid: 38
email: user38@example.com
lower_email: user38@example.com
is_activated: true
is_primary: true
-
id: 39
uid: 39
email: user39@example.com
lower_email: user39@example.com
is_activated: true
is_primary: true
-
id: 40
uid: 40
email: user40@example.com
lower_email: user40@example.com
is_activated: true
is_primary: true
+34
View File
@@ -338,3 +338,37 @@
created_unix: 978307210 created_unix: 978307210
updated_unix: 978307210 updated_unix: 978307210
is_locked: false is_locked: false
-
id: 21
repo_id: 60
index: 1
poster_id: 39
original_author_id: 0
name: repo60 pull1
content: content for the 1st issue
milestone_id: 0
priority: 0
is_closed: false
is_pull: true
num_comments: 0
created_unix: 1707270422
updated_unix: 1707270422
is_locked: false
-
id: 22
repo_id: 61
index: 1
poster_id: 40
original_author_id: 0
name: repo61 pull1
content: content for the 1st issue
milestone_id: 0
priority: 0
is_closed: false
is_pull: true
num_comments: 0
created_unix: 1707270422
updated_unix: 1707270422
is_locked: false
+18
View File
@@ -99,3 +99,21 @@
uid: 5 uid: 5
org_id: 36 org_id: 36
is_public: true is_public: true
-
id: 18
uid: 38
org_id: 41
is_public: true
-
id: 19
uid: 39
org_id: 41
is_public: true
-
id: 20
uid: 40
org_id: 41
is_public: true
+19
View File
@@ -9,6 +9,7 @@
head_branch: branch1 head_branch: branch1
base_branch: master base_branch: master
merge_base: 4a357436d925b5c974181ff12a994538ddc5a269 merge_base: 4a357436d925b5c974181ff12a994538ddc5a269
merged_commit_id: 1a8823cd1a9549fde083f992f6b9b87a7ab74fb3
has_merged: true has_merged: true
merger_id: 2 merger_id: 2
@@ -98,3 +99,21 @@
index: 1 index: 1
head_repo_id: 23 head_repo_id: 23
base_repo_id: 23 base_repo_id: 23
-
id: 9
type: 0 # gitea pull request
status: 2 # mergable
issue_id: 21
index: 1
head_repo_id: 60
base_repo_id: 60
-
id: 10
type: 0 # gitea pull request
status: 2 # mergable
issue_id: 22
index: 1
head_repo_id: 61
base_repo_id: 61
+42
View File
@@ -676,3 +676,45 @@
type: 1 type: 1
config: "{}" config: "{}"
created_unix: 946684810 created_unix: 946684810
-
id: 102
repo_id: 60
type: 1
config: "{}"
created_unix: 946684810
-
id: 103
repo_id: 60
type: 2
config: "{\"EnableTimetracker\":true,\"AllowOnlyContributorsToTrackTime\":true}"
created_unix: 946684810
-
id: 104
repo_id: 60
type: 3
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
created_unix: 946684810
-
id: 105
repo_id: 61
type: 1
config: "{}"
created_unix: 946684810
-
id: 106
repo_id: 61
type: 2
config: "{\"EnableTimetracker\":true,\"AllowOnlyContributorsToTrackTime\":true}"
created_unix: 946684810
-
id: 107
repo_id: 61
type: 3
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
created_unix: 946684810
+62
View File
@@ -1706,3 +1706,65 @@
is_private: true is_private: true
status: 0 status: 0
num_issues: 0 num_issues: 0
-
id: 60
owner_id: 40
owner_name: user40
lower_name: repo60
name: repo60
default_branch: main
num_watches: 0
num_stars: 0
num_forks: 0
num_issues: 0
num_closed_issues: 0
num_pulls: 1
num_closed_pulls: 0
num_milestones: 0
num_closed_milestones: 0
num_projects: 0
num_closed_projects: 0
is_private: false
is_empty: false
is_archived: false
is_mirror: false
status: 0
is_fork: false
fork_id: 0
is_template: false
template_id: 0
size: 0
is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false
-
id: 61
owner_id: 41
owner_name: org41
lower_name: repo61
name: repo61
default_branch: main
num_watches: 0
num_stars: 0
num_forks: 0
num_issues: 0
num_closed_issues: 0
num_pulls: 1
num_closed_pulls: 0
num_milestones: 0
num_closed_milestones: 0
num_projects: 0
num_closed_projects: 0
is_private: false
is_empty: false
is_archived: false
is_mirror: false
status: 0
is_fork: false
fork_id: 0
is_template: false
template_id: 0
size: 0
is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false
+22
View File
@@ -217,3 +217,25 @@
num_members: 1 num_members: 1
includes_all_repositories: false includes_all_repositories: false
can_create_org_repo: true can_create_org_repo: true
-
id: 21
org_id: 41
lower_name: owners
name: Owners
authorize: 4 # owner
num_repos: 1
num_members: 1
includes_all_repositories: true
can_create_org_repo: true
-
id: 22
org_id: 41
lower_name: team1
name: Team1
authorize: 1 # read
num_repos: 1
num_members: 2
includes_all_repositories: false
can_create_org_repo: false
+12
View File
@@ -63,3 +63,15 @@
org_id: 17 org_id: 17
team_id: 9 team_id: 9
repo_id: 24 repo_id: 24
-
id: 12
org_id: 41
team_id: 21
repo_id: 61
-
id: 13
org_id: 41
team_id: 22
repo_id: 61
+36
View File
@@ -286,3 +286,39 @@
team_id: 2 team_id: 2
type: 8 type: 8
access_mode: 2 access_mode: 2
-
id: 49
team_id: 21
type: 1
access_mode: 4
-
id: 50
team_id: 21
type: 2
access_mode: 4
-
id: 51
team_id: 21
type: 3
access_mode: 4
-
id: 52
team_id: 22
type: 1
access_mode: 1
-
id: 53
team_id: 22
type: 2
access_mode: 1
-
id: 54
team_id: 22
type: 3
access_mode: 1
+18
View File
@@ -129,3 +129,21 @@
org_id: 17 org_id: 17
team_id: 9 team_id: 9
uid: 15 uid: 15
-
id: 23
org_id: 41
team_id: 21
uid: 40
-
id: 24
org_id: 41
team_id: 22
uid: 38
-
id: 25
org_id: 41
team_id: 22
uid: 39
+148
View File
@@ -1369,3 +1369,151 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
-
id: 38
lower_name: user38
name: user38
full_name: User38
email: user38@example.com
keep_email_private: false
email_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
login_source: 0
login_name: user38
type: 0
salt: ZogKvWdyEx
max_repo_creation: -1
is_active: true
is_admin: false
is_restricted: false
allow_git_hook: false
allow_import_local: false
allow_create_organization: true
prohibit_login: false
avatar: avatar38
avatar_email: user38@example.com
use_custom_avatar: false
num_followers: 0
num_following: 0
num_stars: 0
num_repos: 0
num_teams: 0
num_members: 0
visibility: 0
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
-
id: 39
lower_name: user39
name: user39
full_name: User39
email: user39@example.com
keep_email_private: false
email_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
login_source: 0
login_name: user39
type: 0
salt: ZogKvWdyEx
max_repo_creation: -1
is_active: true
is_admin: false
is_restricted: false
allow_git_hook: false
allow_import_local: false
allow_create_organization: true
prohibit_login: false
avatar: avatar39
avatar_email: user39@example.com
use_custom_avatar: false
num_followers: 0
num_following: 0
num_stars: 0
num_repos: 0
num_teams: 0
num_members: 0
visibility: 0
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
-
id: 40
lower_name: user40
name: user40
full_name: User40
email: user40@example.com
keep_email_private: false
email_notifications_preference: onmention
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
login_source: 0
login_name: user40
type: 0
salt: ZogKvWdyEx
max_repo_creation: -1
is_active: true
is_admin: false
is_restricted: false
allow_git_hook: false
allow_import_local: false
allow_create_organization: true
prohibit_login: false
avatar: avatar40
avatar_email: user40@example.com
use_custom_avatar: false
num_followers: 0
num_following: 0
num_stars: 0
num_repos: 1
num_teams: 0
num_members: 0
visibility: 0
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
-
id: 41
lower_name: org41
name: org41
full_name: Org41
email: org41@example.com
keep_email_private: false
email_notifications_preference: onmention
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
login_source: 0
login_name: org41
type: 1
salt: ZogKvWdyEx
max_repo_creation: -1
is_active: false
is_admin: false
is_restricted: false
allow_git_hook: false
allow_import_local: false
allow_create_organization: true
prohibit_login: false
avatar: avatar41
avatar_email: org41@example.com
use_custom_avatar: false
num_followers: 0
num_following: 0
num_stars: 0
num_repos: 1
num_teams: 2
num_members: 3
visibility: 0
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
+5 -5
View File
@@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"xorm.io/builder" "xorm.io/builder"
) )
@@ -67,7 +67,7 @@ type FindBranchOptions struct {
db.ListOptions db.ListOptions
RepoID int64 RepoID int64
ExcludeBranchNames []string ExcludeBranchNames []string
IsDeletedBranch util.OptionalBool IsDeletedBranch optional.Option[bool]
OrderBy string OrderBy string
Keyword string Keyword string
} }
@@ -81,8 +81,8 @@ func (opts FindBranchOptions) ToConds() builder.Cond {
if len(opts.ExcludeBranchNames) > 0 { if len(opts.ExcludeBranchNames) > 0 {
cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames)) cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames))
} }
if !opts.IsDeletedBranch.IsNone() { if opts.IsDeletedBranch.Has() {
cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.IsTrue()}) cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.Value()})
} }
if opts.Keyword != "" { if opts.Keyword != "" {
cond = cond.And(builder.Like{"name", opts.Keyword}) cond = cond.And(builder.Like{"name", opts.Keyword})
@@ -92,7 +92,7 @@ func (opts FindBranchOptions) ToConds() builder.Cond {
func (opts FindBranchOptions) ToOrders() string { func (opts FindBranchOptions) ToOrders() string {
orderBy := opts.OrderBy orderBy := opts.OrderBy
if !opts.IsDeletedBranch.IsFalse() { // if deleted branch included, put them at the end if opts.IsDeletedBranch.ValueOrDefault(true) { // if deleted branch included, put them at the end
if orderBy != "" { if orderBy != "" {
orderBy += ", " orderBy += ", "
} }
+2 -2
View File
@@ -13,7 +13,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -50,7 +50,7 @@ func TestGetDeletedBranches(t *testing.T) {
branches, err := db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{ branches, err := db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{
ListOptions: db.ListOptionsAll, ListOptions: db.ListOptionsAll,
RepoID: repo.ID, RepoID: repo.ID,
IsDeletedBranch: util.OptionalBoolTrue, IsDeletedBranch: optional.Some(true),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, branches, 2) assert.Len(t, branches, 2)
+2 -2
View File
@@ -8,7 +8,7 @@ import (
"sort" "sort"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"github.com/gobwas/glob" "github.com/gobwas/glob"
) )
@@ -56,7 +56,7 @@ func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string)
Page: page, Page: page,
}, },
RepoID: repoID, RepoID: repoID,
IsDeletedBranch: util.OptionalBoolFalse, IsDeletedBranch: optional.Some(false),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
+22 -15
View File
@@ -855,6 +855,9 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
// Check comment type. // Check comment type.
switch opts.Type { switch opts.Type {
case CommentTypeCode: case CommentTypeCode:
if err = updateAttachments(ctx, opts, comment); err != nil {
return err
}
if comment.ReviewID != 0 { if comment.ReviewID != 0 {
if comment.Review == nil { if comment.Review == nil {
if err := comment.loadReview(ctx); err != nil { if err := comment.loadReview(ctx); err != nil {
@@ -872,22 +875,9 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
} }
fallthrough fallthrough
case CommentTypeReview: case CommentTypeReview:
// Check attachments if err = updateAttachments(ctx, opts, comment); err != nil {
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments) return err
if err != nil {
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
} }
for i := range attachments {
attachments[i].IssueID = opts.Issue.ID
attachments[i].CommentID = comment.ID
// No assign value could be 0, so ignore AllCols().
if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
}
}
comment.Attachments = attachments
case CommentTypeReopen, CommentTypeClose: case CommentTypeReopen, CommentTypeClose:
if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil { if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
return err return err
@@ -897,6 +887,23 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
return UpdateIssueCols(ctx, opts.Issue, "updated_unix") return UpdateIssueCols(ctx, opts.Issue, "updated_unix")
} }
func updateAttachments(ctx context.Context, opts *CreateCommentOptions, comment *Comment) error {
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
if err != nil {
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
}
for i := range attachments {
attachments[i].IssueID = opts.Issue.ID
attachments[i].CommentID = comment.ID
// No assign value could be 0, so ignore AllCols().
if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
}
}
comment.Attachments = attachments
return nil
}
func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) { func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) {
var content string var content string
var commentType CommentType var commentType CommentType
+1 -1
View File
@@ -379,7 +379,7 @@ func TestCountIssues(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{}) count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{})
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 20, count) assert.EqualValues(t, 22, count)
} }
func TestIssueLoadAttributes(t *testing.T) { func TestIssueLoadAttributes(t *testing.T) {
+49
View File
@@ -652,6 +652,35 @@ func GetPullRequestByIssueID(ctx context.Context, issueID int64) (*PullRequest,
return pr, pr.LoadAttributes(ctx) return pr, pr.LoadAttributes(ctx)
} }
// GetPullRequestsByBaseHeadInfo returns the pull request by given base and head
func GetPullRequestByBaseHeadInfo(ctx context.Context, baseID, headID int64, base, head string) (*PullRequest, error) {
pr := &PullRequest{}
sess := db.GetEngine(ctx).
Join("INNER", "issue", "issue.id = pull_request.issue_id").
Where("base_repo_id = ? AND base_branch = ? AND head_repo_id = ? AND head_branch = ?", baseID, base, headID, head)
has, err := sess.Get(pr)
if err != nil {
return nil, err
}
if !has {
return nil, ErrPullRequestNotExist{
HeadRepoID: headID,
BaseRepoID: baseID,
HeadBranch: head,
BaseBranch: base,
}
}
if err = pr.LoadAttributes(ctx); err != nil {
return nil, err
}
if err = pr.LoadIssue(ctx); err != nil {
return nil, err
}
return pr, nil
}
// GetAllUnmergedAgitPullRequestByPoster get all unmerged agit flow pull request // GetAllUnmergedAgitPullRequestByPoster get all unmerged agit flow pull request
// By poster id. // By poster id.
func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*PullRequest, error) { func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*PullRequest, error) {
@@ -1093,3 +1122,23 @@ func InsertPullRequests(ctx context.Context, prs ...*PullRequest) error {
} }
return committer.Commit() return committer.Commit()
} }
// GetPullRequestByMergedCommit returns a merged pull request by the given commit
func GetPullRequestByMergedCommit(ctx context.Context, repoID int64, sha string) (*PullRequest, error) {
pr := new(PullRequest)
has, err := db.GetEngine(ctx).Where("base_repo_id = ? AND merged_commit_id = ?", repoID, sha).Get(pr)
if err != nil {
return nil, err
} else if !has {
return nil, ErrPullRequestNotExist{0, 0, 0, repoID, "", ""}
}
if err = pr.LoadAttributes(ctx); err != nil {
return nil, err
}
if err = pr.LoadIssue(ctx); err != nil {
return nil, err
}
return pr, nil
}
+12
View File
@@ -339,6 +339,18 @@ func TestGetApprovers(t *testing.T) {
assert.EqualValues(t, expected, approvers) assert.EqualValues(t, expected, approvers)
} }
func TestGetPullRequestByMergedCommit(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
pr, err := issues_model.GetPullRequestByMergedCommit(db.DefaultContext, 1, "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3")
assert.NoError(t, err)
assert.EqualValues(t, 1, pr.ID)
_, err = issues_model.GetPullRequestByMergedCommit(db.DefaultContext, 0, "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3")
assert.ErrorAs(t, err, &issues_model.ErrPullRequestNotExist{})
_, err = issues_model.GetPullRequestByMergedCommit(db.DefaultContext, 1, "")
assert.ErrorAs(t, err, &issues_model.ErrPullRequestNotExist{})
}
func TestMigrate_InsertPullRequests(t *testing.T) { func TestMigrate_InsertPullRequests(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
reponame := "repo1" reponame := "repo1"
+1 -1
View File
@@ -4,9 +4,9 @@
package base package base
import ( import (
"crypto/sha256"
"encoding/hex" "encoding/hex"
"github.com/minio/sha256-simd"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
) )
+1 -1
View File
@@ -4,9 +4,9 @@
package v1_14 //nolint package v1_14 //nolint
import ( import (
"crypto/sha256"
"encoding/hex" "encoding/hex"
"github.com/minio/sha256-simd"
"golang.org/x/crypto/argon2" "golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
+2 -2
View File
@@ -332,7 +332,6 @@ func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.
// CanBeAssigned return true if user can be assigned to issue or pull requests in repo // CanBeAssigned return true if user can be assigned to issue or pull requests in repo
// Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface. // Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
// FIXME: user could send PullRequest also could be assigned???
func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) { func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
if user.IsOrganization() { if user.IsOrganization() {
return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID) return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
@@ -341,7 +340,8 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.
if err != nil { if err != nil {
return false, err return false, err
} }
return perm.CanAccessAny(perm_model.AccessModeWrite, unit.TypeCode, unit.TypeIssues, unit.TypePullRequests), nil return perm.CanAccessAny(perm_model.AccessModeWrite, unit.AllRepoUnitTypes...) ||
perm.CanAccessAny(perm_model.AccessModeRead, unit.TypePullRequests), nil
} }
// HasAccess returns true if user has access to repo // HasAccess returns true if user has access to repo
+11 -10
View File
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@@ -228,10 +229,10 @@ type FindReleasesOptions struct {
RepoID int64 RepoID int64
IncludeDrafts bool IncludeDrafts bool
IncludeTags bool IncludeTags bool
IsPreRelease util.OptionalBool IsPreRelease optional.Option[bool]
IsDraft util.OptionalBool IsDraft optional.Option[bool]
TagNames []string TagNames []string
HasSha1 util.OptionalBool // useful to find draft releases which are created with existing tags HasSha1 optional.Option[bool] // useful to find draft releases which are created with existing tags
} }
func (opts FindReleasesOptions) ToConds() builder.Cond { func (opts FindReleasesOptions) ToConds() builder.Cond {
@@ -246,14 +247,14 @@ func (opts FindReleasesOptions) ToConds() builder.Cond {
if len(opts.TagNames) > 0 { if len(opts.TagNames) > 0 {
cond = cond.And(builder.In("tag_name", opts.TagNames)) cond = cond.And(builder.In("tag_name", opts.TagNames))
} }
if !opts.IsPreRelease.IsNone() { if opts.IsPreRelease.Has() {
cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.IsTrue()}) cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.Value()})
} }
if !opts.IsDraft.IsNone() { if opts.IsDraft.Has() {
cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.IsTrue()}) cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.Value()})
} }
if !opts.HasSha1.IsNone() { if opts.HasSha1.Has() {
if opts.HasSha1.IsTrue() { if opts.HasSha1.Value() {
cond = cond.And(builder.Neq{"sha1": ""}) cond = cond.And(builder.Neq{"sha1": ""})
} else { } else {
cond = cond.And(builder.Eq{"sha1": ""}) cond = cond.And(builder.Eq{"sha1": ""})
@@ -275,7 +276,7 @@ func GetTagNamesByRepoID(ctx context.Context, repoID int64) ([]string, error) {
ListOptions: listOptions, ListOptions: listOptions,
IncludeDrafts: true, IncludeDrafts: true,
IncludeTags: true, IncludeTags: true,
HasSha1: util.OptionalBoolTrue, HasSha1: optional.Some(true),
RepoID: repoID, RepoID: repoID,
} }
+3 -3
View File
@@ -138,12 +138,12 @@ func getTestCases() []struct {
{ {
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse},
count: 31, count: 33,
}, },
{ {
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse},
count: 36, count: 38,
}, },
{ {
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
@@ -158,7 +158,7 @@ func getTestCases() []struct {
{ {
name: "AllPublic/PublicRepositoriesOfOrganization", name: "AllPublic/PublicRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse},
count: 31, count: 33,
}, },
{ {
name: "AllTemplates", name: "AllTemplates",
+3 -1
View File
@@ -8,6 +8,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
@@ -78,7 +79,8 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
if err = e.Table("team_user"). if err = e.Table("team_user").
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id"). Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id"). Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite). Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
Distinct("`team_user`.uid"). Distinct("`team_user`.uid").
Select("`team_user`.uid"). Select("`team_user`.uid").
Find(&additionalUserIDs); err != nil { Find(&additionalUserIDs); err != nil {
+29 -17
View File
@@ -21,9 +21,6 @@ import (
"xorm.io/builder" "xorm.io/builder"
) )
// ErrEmailNotActivated e-mail address has not been activated error
var ErrEmailNotActivated = util.NewInvalidArgumentErrorf("e-mail address has not been activated")
// ErrEmailCharIsNotSupported e-mail address contains unsupported character // ErrEmailCharIsNotSupported e-mail address contains unsupported character
type ErrEmailCharIsNotSupported struct { type ErrEmailCharIsNotSupported struct {
Email string Email string
@@ -313,27 +310,27 @@ func updateActivation(ctx context.Context, email *EmailAddress, activate bool) e
return UpdateUserCols(ctx, user, "rands") return UpdateUserCols(ctx, user, "rands")
} }
// MakeEmailPrimary sets primary email address of given user. func MakeActiveEmailPrimary(ctx context.Context, emailID int64) error {
func MakeEmailPrimary(ctx context.Context, email *EmailAddress) error { return makeEmailPrimaryInternal(ctx, emailID, true)
has, err := db.GetEngine(ctx).Get(email) }
if err != nil {
func MakeInactiveEmailPrimary(ctx context.Context, emailID int64) error {
return makeEmailPrimaryInternal(ctx, emailID, false)
}
func makeEmailPrimaryInternal(ctx context.Context, emailID int64, isActive bool) error {
email := &EmailAddress{}
if has, err := db.GetEngine(ctx).ID(emailID).Where(builder.Eq{"is_activated": isActive}).Get(email); err != nil {
return err return err
} else if !has { } else if !has {
return ErrEmailAddressNotExist{Email: email.Email} return ErrEmailAddressNotExist{}
}
if !email.IsActivated {
return ErrEmailNotActivated
} }
user := &User{} user := &User{}
has, err = db.GetEngine(ctx).ID(email.UID).Get(user) if has, err := db.GetEngine(ctx).ID(email.UID).Get(user); err != nil {
if err != nil {
return err return err
} else if !has { } else if !has {
return ErrUserNotExist{ return ErrUserNotExist{UID: email.UID}
UID: email.UID,
}
} }
ctx, committer, err := db.TxContext(ctx) ctx, committer, err := db.TxContext(ctx)
@@ -365,6 +362,21 @@ func MakeEmailPrimary(ctx context.Context, email *EmailAddress) error {
return committer.Commit() return committer.Commit()
} }
// ChangeInactivePrimaryEmail replaces the inactive primary email of a given user
func ChangeInactivePrimaryEmail(ctx context.Context, uid int64, oldEmailAddr, newEmailAddr string) error {
return db.WithTx(ctx, func(ctx context.Context) error {
_, err := db.GetEngine(ctx).Where(builder.Eq{"uid": uid, "lower_email": strings.ToLower(oldEmailAddr)}).Delete(&EmailAddress{})
if err != nil {
return err
}
newEmail, err := InsertEmailAddress(ctx, &EmailAddress{UID: uid, Email: newEmailAddr})
if err != nil {
return err
}
return MakeInactiveEmailPrimary(ctx, newEmail.ID)
})
}
// VerifyActiveEmailCode verifies active email code when active account // VerifyActiveEmailCode verifies active email code when active account
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress { func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
minutes := setting.Service.ActiveCodeLives minutes := setting.Service.ActiveCodeLives
+9 -18
View File
@@ -45,31 +45,22 @@ func TestIsEmailUsed(t *testing.T) {
func TestMakeEmailPrimary(t *testing.T) { func TestMakeEmailPrimary(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
email := &user_model.EmailAddress{ err := user_model.MakeActiveEmailPrimary(db.DefaultContext, 9999999)
Email: "user567890@example.com",
}
err := user_model.MakeEmailPrimary(db.DefaultContext, email)
assert.Error(t, err) assert.Error(t, err)
assert.EqualError(t, err, user_model.ErrEmailAddressNotExist{Email: email.Email}.Error()) assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{})
email = &user_model.EmailAddress{ email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user11@example.com"})
Email: "user11@example.com", err = user_model.MakeActiveEmailPrimary(db.DefaultContext, email.ID)
}
err = user_model.MakeEmailPrimary(db.DefaultContext, email)
assert.Error(t, err) assert.Error(t, err)
assert.EqualError(t, err, user_model.ErrEmailNotActivated.Error()) assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{}) // inactive email is considered as not exist for "MakeActiveEmailPrimary"
email = &user_model.EmailAddress{ email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user9999999@example.com"})
Email: "user9999999@example.com", err = user_model.MakeActiveEmailPrimary(db.DefaultContext, email.ID)
}
err = user_model.MakeEmailPrimary(db.DefaultContext, email)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, user_model.IsErrUserNotExist(err)) assert.True(t, user_model.IsErrUserNotExist(err))
email = &user_model.EmailAddress{ email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user101@example.com"})
Email: "user101@example.com", err = user_model.MakeActiveEmailPrimary(db.DefaultContext, email.ID)
}
err = user_model.MakeEmailPrimary(db.DefaultContext, email)
assert.NoError(t, err) assert.NoError(t, err)
user, _ := user_model.GetUserByID(db.DefaultContext, int64(10)) user, _ := user_model.GetUserByID(db.DefaultContext, int64(10))
+3
View File
@@ -9,6 +9,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@@ -30,6 +31,8 @@ type SearchUserOptions struct {
Actor *User // The user doing the search Actor *User // The user doing the search
SearchByEmail bool // Search by email as well as username/full name SearchByEmail bool // Search by email as well as username/full name
SupportedSortOrders container.Set[string] // if not nil, only allow to use the sort orders in this set
IsActive util.OptionalBool IsActive util.OptionalBool
IsAdmin util.OptionalBool IsAdmin util.OptionalBool
IsRestricted util.OptionalBool IsRestricted util.OptionalBool
+13 -12
View File
@@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@@ -573,14 +574,14 @@ func IsUsableUsername(name string) error {
// CreateUserOverwriteOptions are an optional options who overwrite system defaults on user creation // CreateUserOverwriteOptions are an optional options who overwrite system defaults on user creation
type CreateUserOverwriteOptions struct { type CreateUserOverwriteOptions struct {
KeepEmailPrivate util.OptionalBool KeepEmailPrivate optional.Option[bool]
Visibility *structs.VisibleType Visibility *structs.VisibleType
AllowCreateOrganization util.OptionalBool AllowCreateOrganization optional.Option[bool]
EmailNotificationsPreference *string EmailNotificationsPreference *string
MaxRepoCreation *int MaxRepoCreation *int
Theme *string Theme *string
IsRestricted util.OptionalBool IsRestricted optional.Option[bool]
IsActive util.OptionalBool IsActive optional.Option[bool]
} }
// CreateUser creates record of a new user. // CreateUser creates record of a new user.
@@ -607,14 +608,14 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
// overwrite defaults if set // overwrite defaults if set
if len(overwriteDefault) != 0 && overwriteDefault[0] != nil { if len(overwriteDefault) != 0 && overwriteDefault[0] != nil {
overwrite := overwriteDefault[0] overwrite := overwriteDefault[0]
if !overwrite.KeepEmailPrivate.IsNone() { if overwrite.KeepEmailPrivate.Has() {
u.KeepEmailPrivate = overwrite.KeepEmailPrivate.IsTrue() u.KeepEmailPrivate = overwrite.KeepEmailPrivate.Value()
} }
if overwrite.Visibility != nil { if overwrite.Visibility != nil {
u.Visibility = *overwrite.Visibility u.Visibility = *overwrite.Visibility
} }
if !overwrite.AllowCreateOrganization.IsNone() { if overwrite.AllowCreateOrganization.Has() {
u.AllowCreateOrganization = overwrite.AllowCreateOrganization.IsTrue() u.AllowCreateOrganization = overwrite.AllowCreateOrganization.Value()
} }
if overwrite.EmailNotificationsPreference != nil { if overwrite.EmailNotificationsPreference != nil {
u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference
@@ -625,11 +626,11 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
if overwrite.Theme != nil { if overwrite.Theme != nil {
u.Theme = *overwrite.Theme u.Theme = *overwrite.Theme
} }
if !overwrite.IsRestricted.IsNone() { if overwrite.IsRestricted.Has() {
u.IsRestricted = overwrite.IsRestricted.IsTrue() u.IsRestricted = overwrite.IsRestricted.Value()
} }
if !overwrite.IsActive.IsNone() { if overwrite.IsActive.Has() {
u.IsActive = overwrite.IsActive.IsTrue() u.IsActive = overwrite.IsActive.Value()
} }
} }
+3 -3
View File
@@ -89,7 +89,7 @@ func TestSearchUsers(t *testing.T) {
[]int64{19, 25}) []int64{19, 25})
testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}}, testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}},
[]int64{26}) []int64{26, 41})
testOrgSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 5, PageSize: 2}}, testOrgSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 5, PageSize: 2}},
[]int64{}) []int64{})
@@ -101,13 +101,13 @@ func TestSearchUsers(t *testing.T) {
} }
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}}, testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}},
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37}) []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse},
[]int64{9}) []int64{9})
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37}) []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) []int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
+3
View File
@@ -441,6 +441,9 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
// all acts conditions should be satisfied // all acts conditions should be satisfied
for cond, vals := range acts { for cond, vals := range acts {
switch cond { switch cond {
case "types":
// types have been checked
continue
case "branches": case "branches":
refName := git.RefName(prPayload.PullRequest.Base.Ref) refName := git.RefName(prPayload.PullRequest.Base.Ref)
patterns, err := workflowpattern.CompilePatterns(vals...) patterns, err := workflowpattern.CompilePatterns(vals...)
+1 -1
View File
@@ -4,12 +4,12 @@
package hash package hash
import ( import (
"crypto/sha256"
"encoding/hex" "encoding/hex"
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"github.com/minio/sha256-simd"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
) )
+1 -2
View File
@@ -4,10 +4,9 @@
package avatar package avatar
import ( import (
"crypto/sha256"
"encoding/hex" "encoding/hex"
"strconv" "strconv"
"github.com/minio/sha256-simd"
) )
// HashAvatar will generate a unique string, which ensures that when there's a // HashAvatar will generate a unique string, which ensures that when there's a
+1 -2
View File
@@ -7,11 +7,10 @@
package identicon package identicon
import ( import (
"crypto/sha256"
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"github.com/minio/sha256-simd"
) )
const minImageSize = 16 const minImageSize = 16
+104
View File
@@ -0,0 +1,104 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package badge
import (
actions_model "code.gitea.io/gitea/models/actions"
)
// The Badge layout: |offset|label|message|
// We use 10x scale to calculate more precisely
// Then scale down to normal size in tmpl file
type Label struct {
text string
width int
}
func (l Label) Text() string {
return l.text
}
func (l Label) Width() int {
return l.width
}
func (l Label) TextLength() int {
return int(float64(l.width-defaultOffset) * 9.5)
}
func (l Label) X() int {
return l.width*5 + 10
}
type Message struct {
text string
width int
x int
}
func (m Message) Text() string {
return m.text
}
func (m Message) Width() int {
return m.width
}
func (m Message) X() int {
return m.x
}
func (m Message) TextLength() int {
return int(float64(m.width-defaultOffset) * 9.5)
}
type Badge struct {
Color string
FontSize int
Label Label
Message Message
}
func (b Badge) Width() int {
return b.Label.width + b.Message.width
}
const (
defaultOffset = 9
defaultFontSize = 11
DefaultColor = "#9f9f9f" // Grey
defaultFontWidth = 7 // approximate speculation
)
var StatusColorMap = map[actions_model.Status]string{
actions_model.StatusSuccess: "#4c1", // Green
actions_model.StatusSkipped: "#dfb317", // Yellow
actions_model.StatusUnknown: "#97ca00", // Light Green
actions_model.StatusFailure: "#e05d44", // Red
actions_model.StatusCancelled: "#fe7d37", // Orange
actions_model.StatusWaiting: "#dfb317", // Yellow
actions_model.StatusRunning: "#dfb317", // Yellow
actions_model.StatusBlocked: "#dfb317", // Yellow
}
// GenerateBadge generates badge with given template
func GenerateBadge(label, message, color string) Badge {
lw := defaultFontWidth*len(label) + defaultOffset
mw := defaultFontWidth*len(message) + defaultOffset
x := lw*10 + mw*5 - 10
return Badge{
Label: Label{
text: label,
width: lw,
},
Message: Message{
text: message,
width: mw,
x: x,
},
FontSize: defaultFontSize * 10,
Color: color,
}
}
+1 -1
View File
@@ -5,6 +5,7 @@ package base
import ( import (
"crypto/sha1" "crypto/sha1"
"crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors" "errors"
@@ -22,7 +23,6 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/minio/sha256-simd"
) )
// EncodeSha1 string to sha1 hex value. // EncodeSha1 string to sha1 hex value.
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"code.gitea.io/gitea/modules/optional"
)
const (
AttributeLinguistVendored = "linguist-vendored"
AttributeLinguistGenerated = "linguist-generated"
AttributeLinguistDocumentation = "linguist-documentation"
AttributeLinguistDetectable = "linguist-detectable"
AttributeLinguistLanguage = "linguist-language"
AttributeGitlabLanguage = "gitlab-language"
)
// true if "set"/"true", false if "unset"/"false", none otherwise
func AttributeToBool(attr map[string]string, name string) optional.Option[bool] {
switch attr[name] {
case "set", "true":
return optional.Some(true)
case "unset", "false":
return optional.Some(false)
}
return optional.None[bool]()
}
func AttributeToString(attr map[string]string, name string) optional.Option[string] {
if value, has := attr[name]; has && value != "unspecified" {
return optional.Some(value)
}
return optional.None[string]()
}
+4
View File
@@ -115,6 +115,10 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
// Close BlameReader - don't run NextPart after invoking that // Close BlameReader - don't run NextPart after invoking that
func (r *BlameReader) Close() error { func (r *BlameReader) Close() error {
if r.bufferedReader == nil {
return nil
}
err := <-r.done err := <-r.done
r.bufferedReader = nil r.bufferedReader = nil
_ = r.reader.Close() _ = r.reader.Close()
+11 -1
View File
@@ -102,7 +102,17 @@ func (b *blobReader) Read(p []byte) (n int, err error) {
// Close implements io.Closer // Close implements io.Closer
func (b *blobReader) Close() error { func (b *blobReader) Close() error {
if b.rd == nil {
return nil
}
defer b.cancel() defer b.cancel()
return DiscardFull(b.rd, b.n+1) if err := DiscardFull(b.rd, b.n+1); err != nil {
return err
}
b.rd = nil
return nil
} }
+20 -18
View File
@@ -33,16 +33,18 @@ var (
// DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx // DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx
DefaultContext context.Context DefaultContext context.Context
SupportProcReceive bool // >= 2.29 DefaultFeatures struct {
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an experimental curiosity GitVersion *version.Version
gitVersion *version.Version SupportProcReceive bool // >= 2.29
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an experimental curiosity
}
) )
// loadGitVersion tries to get the current git version and stores it into a global variable // loadGitVersion tries to get the current git version and stores it into a global variable
func loadGitVersion() error { func loadGitVersion() error {
// doesn't need RWMutex because it's executed by Init() // doesn't need RWMutex because it's executed by Init()
if gitVersion != nil { if DefaultFeatures.GitVersion != nil {
return nil return nil
} }
@@ -53,7 +55,7 @@ func loadGitVersion() error {
ver, err := parseGitVersionLine(strings.TrimSpace(stdout)) ver, err := parseGitVersionLine(strings.TrimSpace(stdout))
if err == nil { if err == nil {
gitVersion = ver DefaultFeatures.GitVersion = ver
} }
return err return err
} }
@@ -93,7 +95,7 @@ func SetExecutablePath(path string) error {
return err return err
} }
if gitVersion.LessThan(versionRequired) { if DefaultFeatures.GitVersion.LessThan(versionRequired) {
moreHint := "get git: https://git-scm.com/download/" moreHint := "get git: https://git-scm.com/download/"
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them // there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
@@ -102,22 +104,22 @@ func SetExecutablePath(path string) error {
moreHint = "get git: https://git-scm.com/download/linux and https://ius.io" moreHint = "get git: https://git-scm.com/download/linux and https://ius.io"
} }
} }
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", gitVersion.Original(), RequiredVersion, moreHint) return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures.GitVersion.Original(), RequiredVersion, moreHint)
} }
if err = checkGitVersionCompatibility(gitVersion); err != nil { if err = checkGitVersionCompatibility(DefaultFeatures.GitVersion); err != nil {
return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", gitVersion.String(), err) return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", DefaultFeatures.GitVersion.String(), err)
} }
return nil return nil
} }
// VersionInfo returns git version information // VersionInfo returns git version information
func VersionInfo() string { func VersionInfo() string {
if gitVersion == nil { if DefaultFeatures.GitVersion == nil {
return "(git not found)" return "(git not found)"
} }
format := "%s" format := "%s"
args := []any{gitVersion.Original()} args := []any{DefaultFeatures.GitVersion.Original()}
// Since git wire protocol has been released from git v2.18 // Since git wire protocol has been released from git v2.18
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
format += ", Wire Protocol %s Enabled" format += ", Wire Protocol %s Enabled"
@@ -187,9 +189,9 @@ func InitFull(ctx context.Context) (err error) {
if CheckGitVersionAtLeast("2.9") == nil { if CheckGitVersionAtLeast("2.9") == nil {
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
} }
SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil DefaultFeatures.SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil
SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil && !isGogit DefaultFeatures.SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil && !isGogit
if SupportHashSha256 { if DefaultFeatures.SupportHashSha256 {
SupportedObjectFormats = append(SupportedObjectFormats, Sha256ObjectFormat) SupportedObjectFormats = append(SupportedObjectFormats, Sha256ObjectFormat)
} else { } else {
log.Warn("sha256 hash support is disabled - requires Git >= 2.42. Gogit is currently unsupported") log.Warn("sha256 hash support is disabled - requires Git >= 2.42. Gogit is currently unsupported")
@@ -254,7 +256,7 @@ func syncGitConfig() (err error) {
} }
} }
if SupportProcReceive { if DefaultFeatures.SupportProcReceive {
// set support for AGit flow // set support for AGit flow
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil { if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
return err return err
@@ -309,15 +311,15 @@ func syncGitConfig() (err error) {
// CheckGitVersionAtLeast check git version is at least the constraint version // CheckGitVersionAtLeast check git version is at least the constraint version
func CheckGitVersionAtLeast(atLeast string) error { func CheckGitVersionAtLeast(atLeast string) error {
if gitVersion == nil { if DefaultFeatures.GitVersion == nil {
panic("git module is not initialized") // it shouldn't happen panic("git module is not initialized") // it shouldn't happen
} }
atLeastVersion, err := version.NewVersion(atLeast) atLeastVersion, err := version.NewVersion(atLeast)
if err != nil { if err != nil {
return err return err
} }
if gitVersion.Compare(atLeastVersion) < 0 { if DefaultFeatures.GitVersion.Compare(atLeastVersion) < 0 {
return fmt.Errorf("installed git binary version %s is not at least %s", gitVersion.Original(), atLeast) return fmt.Errorf("installed git binary version %s is not at least %s", DefaultFeatures.GitVersion.Original(), atLeast)
} }
return nil return nil
} }
+1 -2
View File
@@ -4,12 +4,11 @@
package git package git
import ( import (
"crypto/sha256"
"fmt" "fmt"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/minio/sha256-simd"
) )
// Cache represents a caching interface // Cache represents a caching interface
+1 -1
View File
@@ -101,7 +101,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
if !IsValidObjectFormat(objectFormatName) { if !IsValidObjectFormat(objectFormatName) {
return fmt.Errorf("invalid object format: %s", objectFormatName) return fmt.Errorf("invalid object format: %s", objectFormatName)
} }
if SupportHashSha256 { if DefaultFeatures.SupportHashSha256 {
cmd.AddOptionValues("--object-format", objectFormatName) cmd.AddOptionValues("--object-format", objectFormatName)
} }
+11 -4
View File
@@ -291,10 +291,17 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe
} }
checker := &CheckAttributeReader{ checker := &CheckAttributeReader{
Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, Attributes: []string{
Repo: repo, AttributeLinguistVendored,
IndexFile: indexFilename, AttributeLinguistGenerated,
WorkTree: worktree, AttributeLinguistDocumentation,
AttributeLinguistDetectable,
AttributeLinguistLanguage,
AttributeGitlabLanguage,
},
Repo: repo,
IndexFile: indexFilename,
WorkTree: worktree,
} }
ctx, cancel := context.WithCancel(repo.Ctx) ctx, cancel := context.WithCancel(repo.Ctx)
if err := checker.Init(ctx); err != nil { if err := checker.Init(ctx); err != nil {
+5 -5
View File
@@ -24,7 +24,7 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
select { select {
case attr := <-wr.ReadAttribute(): case attr := <-wr.ReadAttribute():
assert.Equal(t, ".gitignore\"\n", attr.Filename) assert.Equal(t, ".gitignore\"\n", attr.Filename)
assert.Equal(t, "linguist-vendored", attr.Attribute) assert.Equal(t, AttributeLinguistVendored, attr.Attribute)
assert.Equal(t, "unspecified", attr.Value) assert.Equal(t, "unspecified", attr.Value)
case <-time.After(100 * time.Millisecond): case <-time.After(100 * time.Millisecond):
assert.FailNow(t, "took too long to read an attribute from the list") assert.FailNow(t, "took too long to read an attribute from the list")
@@ -38,7 +38,7 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
select { select {
case attr := <-wr.ReadAttribute(): case attr := <-wr.ReadAttribute():
assert.Equal(t, ".gitignore\"\n", attr.Filename) assert.Equal(t, ".gitignore\"\n", attr.Filename)
assert.Equal(t, "linguist-vendored", attr.Attribute) assert.Equal(t, AttributeLinguistVendored, attr.Attribute)
assert.Equal(t, "unspecified", attr.Value) assert.Equal(t, "unspecified", attr.Value)
case <-time.After(100 * time.Millisecond): case <-time.After(100 * time.Millisecond):
assert.FailNow(t, "took too long to read an attribute from the list") assert.FailNow(t, "took too long to read an attribute from the list")
@@ -77,21 +77,21 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, attributeTriple{ assert.EqualValues(t, attributeTriple{
Filename: "shouldbe.vendor", Filename: "shouldbe.vendor",
Attribute: "linguist-vendored", Attribute: AttributeLinguistVendored,
Value: "set", Value: "set",
}, attr) }, attr)
attr = <-wr.ReadAttribute() attr = <-wr.ReadAttribute()
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, attributeTriple{ assert.EqualValues(t, attributeTriple{
Filename: "shouldbe.vendor", Filename: "shouldbe.vendor",
Attribute: "linguist-generated", Attribute: AttributeLinguistGenerated,
Value: "unspecified", Value: "unspecified",
}, attr) }, attr)
attr = <-wr.ReadAttribute() attr = <-wr.ReadAttribute()
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, attributeTriple{ assert.EqualValues(t, attributeTriple{
Filename: "shouldbe.vendor", Filename: "shouldbe.vendor",
Attribute: "linguist-language", Attribute: AttributeLinguistLanguage,
Value: "unspecified", Value: "unspecified",
}, attr) }, attr)
} }
+4 -3
View File
@@ -88,16 +88,17 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
} }
// Close this repository, in particular close the underlying gogitStorage if this is not nil // Close this repository, in particular close the underlying gogitStorage if this is not nil
func (repo *Repository) Close() (err error) { func (repo *Repository) Close() error {
if repo == nil || repo.gogitStorage == nil { if repo == nil || repo.gogitStorage == nil {
return return nil
} }
if err := repo.gogitStorage.Close(); err != nil { if err := repo.gogitStorage.Close(); err != nil {
gitealog.Error("Error closing storage: %v", err) gitealog.Error("Error closing storage: %v", err)
} }
repo.gogitStorage = nil
repo.LastCommitCache = nil repo.LastCommitCache = nil
repo.tagCache = nil repo.tagCache = nil
return return nil
} }
// GoGitRepo gets the go-git repo representation // GoGitRepo gets the go-git repo representation
+2 -2
View File
@@ -103,7 +103,7 @@ func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError
} }
} }
func (repo *Repository) Close() (err error) { func (repo *Repository) Close() error {
if repo == nil { if repo == nil {
return nil return nil
} }
@@ -123,5 +123,5 @@ func (repo *Repository) Close() (err error) {
} }
repo.LastCommitCache = nil repo.LastCommitCache = nil
repo.tagCache = nil repo.tagCache = nil
return err return nil
} }
+19
View File
@@ -6,6 +6,8 @@ package git
import ( import (
"strings" "strings"
"unicode" "unicode"
"code.gitea.io/gitea/modules/optional"
) )
const ( const (
@@ -46,3 +48,20 @@ func mergeLanguageStats(stats map[string]int64) map[string]int64 {
} }
return res return res
} }
func TryReadLanguageAttribute(attrs map[string]string) optional.Option[string] {
language := AttributeToString(attrs, AttributeLinguistLanguage)
if language.Value() == "" {
language = AttributeToString(attrs, AttributeGitlabLanguage)
if language.Has() {
raw := language.Value()
// gitlab-language may have additional parameters after the language
// ignore them and just use the main language
// https://docs.gitlab.com/ee/user/project/highlighting.html#override-syntax-highlighting-for-a-file-type
if idx := strings.IndexByte(raw, '?'); idx >= 0 {
language = optional.Some(raw[:idx])
}
}
}
return language
}
+33 -36
View File
@@ -8,9 +8,9 @@ package git
import ( import (
"bytes" "bytes"
"io" "io"
"strings"
"code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/analyze"
"code.gitea.io/gitea/modules/optional"
"github.com/go-enry/go-enry/v2" "github.com/go-enry/go-enry/v2"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
@@ -57,25 +57,38 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
return nil return nil
} }
notVendored := false isVendored := optional.None[bool]()
notGenerated := false isGenerated := optional.None[bool]()
isDocumentation := optional.None[bool]()
isDetectable := optional.None[bool]()
if checker != nil { if checker != nil {
attrs, err := checker.CheckPath(f.Name) attrs, err := checker.CheckPath(f.Name)
if err == nil { if err == nil {
if vendored, has := attrs["linguist-vendored"]; has { isVendored = AttributeToBool(attrs, AttributeLinguistVendored)
if vendored == "set" || vendored == "true" { if isVendored.ValueOrDefault(false) {
return nil return nil
}
notVendored = vendored == "false"
} }
if generated, has := attrs["linguist-generated"]; has {
if generated == "set" || generated == "true" { isGenerated = AttributeToBool(attrs, AttributeLinguistGenerated)
return nil if isGenerated.ValueOrDefault(false) {
} return nil
notGenerated = generated == "false"
} }
if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" {
isDocumentation = AttributeToBool(attrs, AttributeLinguistDocumentation)
if isDocumentation.ValueOrDefault(false) {
return nil
}
isDetectable = AttributeToBool(attrs, AttributeLinguistDetectable)
if !isDetectable.ValueOrDefault(true) {
return nil
}
hasLanguage := TryReadLanguageAttribute(attrs)
if hasLanguage.Value() != "" {
language := hasLanguage.Value()
// group languages, such as Pug -> HTML; SCSS -> CSS // group languages, such as Pug -> HTML; SCSS -> CSS
group := enry.GetLanguageGroup(language) group := enry.GetLanguageGroup(language)
if len(group) != 0 { if len(group) != 0 {
@@ -85,28 +98,14 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
// this language will always be added to the size // this language will always be added to the size
sizes[language] += f.Size sizes[language] += f.Size
return nil return nil
} else if language, has := attrs["gitlab-language"]; has && language != "unspecified" && language != "" {
// strip off a ? if present
if idx := strings.IndexByte(language, '?'); idx >= 0 {
language = language[:idx]
}
if len(language) != 0 {
// group languages, such as Pug -> HTML; SCSS -> CSS
group := enry.GetLanguageGroup(language)
if len(group) != 0 {
language = group
}
// this language will always be added to the size
sizes[language] += f.Size
return nil
}
} }
} }
} }
if (!notVendored && analyze.IsVendor(f.Name)) || enry.IsDotFile(f.Name) || if (!isVendored.Has() && analyze.IsVendor(f.Name)) ||
enry.IsDocumentation(f.Name) || enry.IsConfiguration(f.Name) { enry.IsDotFile(f.Name) ||
(!isDocumentation.Has() && enry.IsDocumentation(f.Name)) ||
enry.IsConfiguration(f.Name) {
return nil return nil
} }
@@ -115,12 +114,10 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
if f.Size <= bigFileSize { if f.Size <= bigFileSize {
content, _ = readFile(f, fileSizeLimit) content, _ = readFile(f, fileSizeLimit)
} }
if !notGenerated && enry.IsGenerated(f.Name, content) { if !isGenerated.Has() && enry.IsGenerated(f.Name, content) {
return nil return nil
} }
// TODO: Use .gitattributes file for linguist overrides
language := analyze.GetCodeLanguage(f.Name, content) language := analyze.GetCodeLanguage(f.Name, content)
if language == enry.OtherLanguage || language == "" { if language == enry.OtherLanguage || language == "" {
return nil return nil
@@ -138,7 +135,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
included = langtype == enry.Programming || langtype == enry.Markup included = langtype == enry.Programming || langtype == enry.Markup
includedLanguage[language] = included includedLanguage[language] = included
} }
if included { if included || isDetectable.ValueOrDefault(false) {
sizes[language] += f.Size sizes[language] += f.Size
} else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) { } else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) {
firstExcludedLanguage = language firstExcludedLanguage = language
+33 -36
View File
@@ -8,10 +8,10 @@ package git
import ( import (
"bytes" "bytes"
"io" "io"
"strings"
"code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/analyze"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"github.com/go-enry/go-enry/v2" "github.com/go-enry/go-enry/v2"
) )
@@ -88,25 +88,38 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
continue continue
} }
notVendored := false isVendored := optional.None[bool]()
notGenerated := false isGenerated := optional.None[bool]()
isDocumentation := optional.None[bool]()
isDetectable := optional.None[bool]()
if checker != nil { if checker != nil {
attrs, err := checker.CheckPath(f.Name()) attrs, err := checker.CheckPath(f.Name())
if err == nil { if err == nil {
if vendored, has := attrs["linguist-vendored"]; has { isVendored = AttributeToBool(attrs, AttributeLinguistVendored)
if vendored == "set" || vendored == "true" { if isVendored.ValueOrDefault(false) {
continue continue
}
notVendored = vendored == "false"
} }
if generated, has := attrs["linguist-generated"]; has {
if generated == "set" || generated == "true" { isGenerated = AttributeToBool(attrs, AttributeLinguistGenerated)
continue if isGenerated.ValueOrDefault(false) {
} continue
notGenerated = generated == "false"
} }
if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" {
isDocumentation = AttributeToBool(attrs, AttributeLinguistDocumentation)
if isDocumentation.ValueOrDefault(false) {
continue
}
isDetectable = AttributeToBool(attrs, AttributeLinguistDetectable)
if !isDetectable.ValueOrDefault(true) {
continue
}
hasLanguage := TryReadLanguageAttribute(attrs)
if hasLanguage.Value() != "" {
language := hasLanguage.Value()
// group languages, such as Pug -> HTML; SCSS -> CSS // group languages, such as Pug -> HTML; SCSS -> CSS
group := enry.GetLanguageGroup(language) group := enry.GetLanguageGroup(language)
if len(group) != 0 { if len(group) != 0 {
@@ -116,29 +129,14 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
// this language will always be added to the size // this language will always be added to the size
sizes[language] += f.Size() sizes[language] += f.Size()
continue continue
} else if language, has := attrs["gitlab-language"]; has && language != "unspecified" && language != "" {
// strip off a ? if present
if idx := strings.IndexByte(language, '?'); idx >= 0 {
language = language[:idx]
}
if len(language) != 0 {
// group languages, such as Pug -> HTML; SCSS -> CSS
group := enry.GetLanguageGroup(language)
if len(group) != 0 {
language = group
}
// this language will always be added to the size
sizes[language] += f.Size()
continue
}
} }
} }
} }
if (!notVendored && analyze.IsVendor(f.Name())) || enry.IsDotFile(f.Name()) || if (!isVendored.Has() && analyze.IsVendor(f.Name())) ||
enry.IsDocumentation(f.Name()) || enry.IsConfiguration(f.Name()) { enry.IsDotFile(f.Name()) ||
(!isDocumentation.Has() && enry.IsDocumentation(f.Name())) ||
enry.IsConfiguration(f.Name()) {
continue continue
} }
@@ -170,7 +168,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
return nil, err return nil, err
} }
} }
if !notGenerated && enry.IsGenerated(f.Name(), content) { if !isGenerated.Has() && enry.IsGenerated(f.Name(), content) {
continue continue
} }
@@ -193,13 +191,12 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
included = langType == enry.Programming || langType == enry.Markup included = langType == enry.Programming || langType == enry.Markup
includedLanguage[language] = included includedLanguage[language] = included
} }
if included { if included || isDetectable.ValueOrDefault(false) {
sizes[language] += f.Size() sizes[language] += f.Size()
} else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) { } else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) {
firstExcludedLanguage = language firstExcludedLanguage = language
firstExcludedLanguageSize += f.Size() firstExcludedLanguageSize += f.Size()
} }
continue
} }
// If there are no included languages add the first excluded language // If there are no included languages add the first excluded language
+1 -1
View File
@@ -92,7 +92,7 @@ func (i *Indexer) Ping(_ context.Context) error {
} }
func (i *Indexer) Close() { func (i *Indexer) Close() {
if i == nil { if i == nil || i.Indexer == nil {
return return
} }
@@ -87,8 +87,5 @@ func (i *Indexer) Close() {
if i == nil { if i == nil {
return return
} }
if i.Client == nil {
return
}
i.Client = nil i.Client = nil
} }
+7 -7
View File
@@ -218,7 +218,7 @@ func searchIssueIsPull(t *testing.T) {
SearchOptions{ SearchOptions{
IsPull: util.OptionalBoolTrue, IsPull: util.OptionalBoolTrue,
}, },
[]int64{12, 11, 20, 19, 9, 8, 3, 2}, []int64{22, 21, 12, 11, 20, 19, 9, 8, 3, 2},
}, },
} }
for _, test := range tests { for _, test := range tests {
@@ -239,7 +239,7 @@ func searchIssueIsClosed(t *testing.T) {
SearchOptions{ SearchOptions{
IsClosed: util.OptionalBoolFalse, IsClosed: util.OptionalBoolFalse,
}, },
[]int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 19, 18, 10, 7, 9, 8, 3, 2, 1}, []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 19, 18, 10, 7, 9, 8, 3, 2, 1},
}, },
{ {
SearchOptions{ SearchOptions{
@@ -305,7 +305,7 @@ func searchIssueByLabelID(t *testing.T) {
SearchOptions{ SearchOptions{
ExcludedLabelIDs: []int64{1}, ExcludedLabelIDs: []int64{1},
}, },
[]int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3}, []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3},
}, },
} }
for _, test := range tests { for _, test := range tests {
@@ -329,7 +329,7 @@ func searchIssueByTime(t *testing.T) {
SearchOptions{ SearchOptions{
UpdatedAfterUnix: int64Pointer(0), UpdatedAfterUnix: int64Pointer(0),
}, },
[]int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1}, []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1},
}, },
} }
for _, test := range tests { for _, test := range tests {
@@ -350,7 +350,7 @@ func searchIssueWithOrder(t *testing.T) {
SearchOptions{ SearchOptions{
SortBy: internal.SortByCreatedAsc, SortBy: internal.SortByCreatedAsc,
}, },
[]int64{1, 2, 3, 8, 9, 4, 7, 10, 18, 19, 5, 6, 20, 11, 12, 13, 14, 15, 16, 17}, []int64{1, 2, 3, 8, 9, 4, 7, 10, 18, 19, 5, 6, 20, 11, 12, 13, 14, 15, 16, 17, 21, 22},
}, },
} }
for _, test := range tests { for _, test := range tests {
@@ -410,8 +410,8 @@ func searchIssueWithPaginator(t *testing.T) {
PageSize: 5, PageSize: 5,
}, },
}, },
[]int64{17, 16, 15, 14, 13}, []int64{22, 21, 17, 16, 15},
20, 22,
}, },
} }
for _, test := range tests { for _, test := range tests {
+1 -2
View File
@@ -4,6 +4,7 @@
package lfs package lfs
import ( import (
"crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
"hash" "hash"
@@ -12,8 +13,6 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"github.com/minio/sha256-simd"
) )
var ( var (
+1 -2
View File
@@ -4,6 +4,7 @@
package lfs package lfs
import ( import (
"crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
@@ -12,8 +13,6 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/minio/sha256-simd"
) )
const ( const (
+12 -11
View File
@@ -1,48 +1,49 @@
// Copyright 2024 The Gitea Authors. All rights reserved. // Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package optional package optional_test
import ( import (
"testing" "testing"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestOption(t *testing.T) { func TestOption(t *testing.T) {
var uninitialized Option[int] var uninitialized optional.Option[int]
assert.False(t, uninitialized.Has()) assert.False(t, uninitialized.Has())
assert.Equal(t, int(0), uninitialized.Value()) assert.Equal(t, int(0), uninitialized.Value())
assert.Equal(t, int(1), uninitialized.ValueOrDefault(1)) assert.Equal(t, int(1), uninitialized.ValueOrDefault(1))
none := None[int]() none := optional.None[int]()
assert.False(t, none.Has()) assert.False(t, none.Has())
assert.Equal(t, int(0), none.Value()) assert.Equal(t, int(0), none.Value())
assert.Equal(t, int(1), none.ValueOrDefault(1)) assert.Equal(t, int(1), none.ValueOrDefault(1))
some := Some[int](1) some := optional.Some[int](1)
assert.True(t, some.Has()) assert.True(t, some.Has())
assert.Equal(t, int(1), some.Value()) assert.Equal(t, int(1), some.Value())
assert.Equal(t, int(1), some.ValueOrDefault(2)) assert.Equal(t, int(1), some.ValueOrDefault(2))
var ptr *int var ptr *int
assert.False(t, FromPtr(ptr).Has()) assert.False(t, optional.FromPtr(ptr).Has())
opt1 := FromPtr(util.ToPointer(1)) int1 := 1
opt1 := optional.FromPtr(&int1)
assert.True(t, opt1.Has()) assert.True(t, opt1.Has())
assert.Equal(t, int(1), opt1.Value()) assert.Equal(t, int(1), opt1.Value())
assert.False(t, FromNonDefault("").Has()) assert.False(t, optional.FromNonDefault("").Has())
opt2 := FromNonDefault("test") opt2 := optional.FromNonDefault("test")
assert.True(t, opt2.Has()) assert.True(t, opt2.Has())
assert.Equal(t, "test", opt2.Value()) assert.Equal(t, "test", opt2.Value())
assert.False(t, FromNonDefault(0).Has()) assert.False(t, optional.FromNonDefault(0).Has())
opt3 := FromNonDefault(1) opt3 := optional.FromNonDefault(1)
assert.True(t, opt3.Has()) assert.True(t, opt3.Has())
assert.Equal(t, int(1), opt3.Value()) assert.Equal(t, int(1), opt3.Value())
} }
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package optional
import (
"code.gitea.io/gitea/modules/json"
"gopkg.in/yaml.v3"
)
func (o *Option[T]) UnmarshalJSON(data []byte) error {
var v *T
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*o = FromPtr(v)
return nil
}
func (o Option[T]) MarshalJSON() ([]byte, error) {
if !o.Has() {
return []byte("null"), nil
}
return json.Marshal(o.Value())
}
func (o *Option[T]) UnmarshalYAML(value *yaml.Node) error {
var v *T
if err := value.Decode(&v); err != nil {
return err
}
*o = FromPtr(v)
return nil
}
func (o Option[T]) MarshalYAML() (interface{}, error) {
if !o.Has() {
return nil, nil
}
value := new(yaml.Node)
err := value.Encode(o.Value())
return value, err
}
+190
View File
@@ -0,0 +1,190 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package optional_test
import (
std_json "encoding/json" //nolint:depguard
"testing"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
type testSerializationStruct struct {
NormalString string `json:"normal_string" yaml:"normal_string"`
NormalBool bool `json:"normal_bool" yaml:"normal_bool"`
OptBool optional.Option[bool] `json:"optional_bool,omitempty" yaml:"optional_bool,omitempty"`
OptString optional.Option[string] `json:"optional_string,omitempty" yaml:"optional_string,omitempty"`
OptTwoBool optional.Option[bool] `json:"optional_two_bool" yaml:"optional_two_bool"`
OptTwoString optional.Option[string] `json:"optional_twostring" yaml:"optional_two_string"`
}
func TestOptionalToJson(t *testing.T) {
tests := []struct {
name string
obj *testSerializationStruct
want string
}{
{
name: "empty",
obj: new(testSerializationStruct),
want: `{"normal_string":"","normal_bool":false,"optional_two_bool":null,"optional_twostring":null}`,
},
{
name: "some",
obj: &testSerializationStruct{
NormalString: "a string",
NormalBool: true,
OptBool: optional.Some(false),
OptString: optional.Some(""),
OptTwoBool: optional.None[bool](),
OptTwoString: optional.None[string](),
},
want: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_twostring":null}`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
b, err := json.Marshal(tc.obj)
assert.NoError(t, err)
assert.EqualValues(t, tc.want, string(b), "gitea json module returned unexpected")
b, err = std_json.Marshal(tc.obj)
assert.NoError(t, err)
assert.EqualValues(t, tc.want, string(b), "std json module returned unexpected")
})
}
}
func TestOptionalFromJson(t *testing.T) {
tests := []struct {
name string
data string
want testSerializationStruct
}{
{
name: "empty",
data: `{}`,
want: testSerializationStruct{
NormalString: "",
},
},
{
name: "some",
data: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_twostring":null}`,
want: testSerializationStruct{
NormalString: "a string",
NormalBool: true,
OptBool: optional.Some(false),
OptString: optional.Some(""),
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var obj1 testSerializationStruct
err := json.Unmarshal([]byte(tc.data), &obj1)
assert.NoError(t, err)
assert.EqualValues(t, tc.want, obj1, "gitea json module returned unexpected")
var obj2 testSerializationStruct
err = std_json.Unmarshal([]byte(tc.data), &obj2)
assert.NoError(t, err)
assert.EqualValues(t, tc.want, obj2, "std json module returned unexpected")
})
}
}
func TestOptionalToYaml(t *testing.T) {
tests := []struct {
name string
obj *testSerializationStruct
want string
}{
{
name: "empty",
obj: new(testSerializationStruct),
want: `normal_string: ""
normal_bool: false
optional_two_bool: null
optional_two_string: null
`,
},
{
name: "some",
obj: &testSerializationStruct{
NormalString: "a string",
NormalBool: true,
OptBool: optional.Some(false),
OptString: optional.Some(""),
},
want: `normal_string: a string
normal_bool: true
optional_bool: false
optional_string: ""
optional_two_bool: null
optional_two_string: null
`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
b, err := yaml.Marshal(tc.obj)
assert.NoError(t, err)
assert.EqualValues(t, tc.want, string(b), "yaml module returned unexpected")
})
}
}
func TestOptionalFromYaml(t *testing.T) {
tests := []struct {
name string
data string
want testSerializationStruct
}{
{
name: "empty",
data: ``,
want: testSerializationStruct{},
},
{
name: "empty but init",
data: `normal_string: ""
normal_bool: false
optional_bool:
optional_two_bool:
optional_two_string:
`,
want: testSerializationStruct{},
},
{
name: "some",
data: `
normal_string: a string
normal_bool: true
optional_bool: false
optional_string: ""
optional_two_bool: null
optional_twostring: null
`,
want: testSerializationStruct{
NormalString: "a string",
NormalBool: true,
OptBool: optional.Some(false),
OptString: optional.Some(""),
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var obj testSerializationStruct
err := yaml.Unmarshal([]byte(tc.data), &obj)
assert.NoError(t, err)
assert.EqualValues(t, tc.want, obj, "yaml module returned unexpected")
})
}
}
+2
View File
@@ -34,6 +34,8 @@ const (
RepositoryPackage = "_alpine" RepositoryPackage = "_alpine"
RepositoryVersion = "_repository" RepositoryVersion = "_repository"
NoArch = "noarch"
) )
// https://wiki.alpinelinux.org/wiki/Apk_spec // https://wiki.alpinelinux.org/wiki/Apk_spec
-68
View File
@@ -6,22 +6,18 @@ package repository
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"time"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
asymkey_service "code.gitea.io/gitea/services/asymkey"
) )
type OptionFile struct { type OptionFile struct {
@@ -124,70 +120,6 @@ func LoadRepoConfig() error {
return nil return nil
} }
// InitRepoCommit temporarily changes with work directory.
func InitRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) {
commitTimeStr := time.Now().Format(time.RFC3339)
sig := u.NewGitSig()
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+sig.Name,
"GIT_AUTHOR_EMAIL="+sig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
committerName := sig.Name
committerEmail := sig.Email
if stdout, _, err := git.NewCommand(ctx, "add", "--all").
SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)).
RunStdString(&git.RunOpts{Dir: tmpPath}); err != nil {
log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err)
return fmt.Errorf("git add --all: %w", err)
}
cmd := git.NewCommand(ctx, "commit", "--message=Initial commit").
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)
sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u)
if sign {
cmd.AddOptionFormat("-S%s", keyID)
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
// need to set the committer to the KeyID owner
committerName = signer.Name
committerEmail = signer.Email
}
} else {
cmd.AddArguments("--no-gpg-sign")
}
env = append(env,
"GIT_COMMITTER_NAME="+committerName,
"GIT_COMMITTER_EMAIL="+committerEmail,
)
if stdout, _, err := cmd.
SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)).
RunStdString(&git.RunOpts{Dir: tmpPath, Env: env}); err != nil {
log.Error("Failed to commit: %v: Stdout: %s\nError: %v", cmd.String(), stdout, err)
return fmt.Errorf("git commit: %w", err)
}
if len(defaultBranch) == 0 {
defaultBranch = setting.Repository.DefaultBranch
}
if stdout, _, err := git.NewCommand(ctx, "push", "origin").AddDynamicArguments("HEAD:" + defaultBranch).
SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)).
RunStdString(&git.RunOpts{Dir: tmpPath, Env: InternalPushingEnvironment(u, repo)}); err != nil {
log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err)
return fmt.Errorf("git push: %w", err)
}
return nil
}
func CheckInitRepository(ctx context.Context, owner, name, objectFormatName string) (err error) { func CheckInitRepository(ctx context.Context, owner, name, objectFormatName string) (err error) {
// Somehow the directory could exist. // Somehow the directory could exist.
repoPath := repo_model.RepoPath(owner, name) repoPath := repo_model.RepoPath(owner, name)
+1 -2
View File
@@ -7,13 +7,12 @@ import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"github.com/minio/sha256-simd"
) )
// AesEncrypt encrypts text and given key with AES. // AesEncrypt encrypts text and given key with AES.
+9 -1
View File
@@ -3,14 +3,22 @@
package setting package setting
import "code.gitea.io/gitea/modules/container"
// Admin settings // Admin settings
var Admin struct { var Admin struct {
DisableRegularOrgCreation bool DisableRegularOrgCreation bool
DefaultEmailNotification string DefaultEmailNotification string
UserDisabledFeatures container.Set[string]
} }
func loadAdminFrom(rootCfg ConfigProvider) { func loadAdminFrom(rootCfg ConfigProvider) {
mustMapSetting(rootCfg, "admin", &Admin)
sec := rootCfg.Section("admin") sec := rootCfg.Section("admin")
Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)
Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled") Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
} }
const (
UserFeatureDeletion = "deletion"
)
+46 -3
View File
@@ -15,8 +15,45 @@ type PictureStruct struct {
EnableFederatedAvatar *config.Value[bool] EnableFederatedAvatar *config.Value[bool]
} }
type OpenWithEditorApp struct {
DisplayName string
OpenURL string
}
type OpenWithEditorAppsType []OpenWithEditorApp
func (t OpenWithEditorAppsType) ToTextareaString() string {
ret := ""
for _, app := range t {
ret += app.DisplayName + " = " + app.OpenURL + "\n"
}
return ret
}
func DefaultOpenWithEditorApps() OpenWithEditorAppsType {
return OpenWithEditorAppsType{
{
DisplayName: "VS Code",
OpenURL: "vscode://vscode.git/clone?url={url}",
},
{
DisplayName: "VSCodium",
OpenURL: "vscodium://vscode.git/clone?url={url}",
},
{
DisplayName: "Intellij IDEA",
OpenURL: "jetbrains://idea/checkout/git?idea.required.plugins.id=Git4Idea&checkout.repo={url}",
},
}
}
type RepositoryStruct struct {
OpenWithEditorApps *config.Value[OpenWithEditorAppsType]
}
type ConfigStruct struct { type ConfigStruct struct {
Picture *PictureStruct Picture *PictureStruct
Repository *RepositoryStruct
} }
var ( var (
@@ -28,8 +65,11 @@ func initDefaultConfig() {
config.SetCfgSecKeyGetter(&cfgSecKeyGetter{}) config.SetCfgSecKeyGetter(&cfgSecKeyGetter{})
defaultConfig = &ConfigStruct{ defaultConfig = &ConfigStruct{
Picture: &PictureStruct{ Picture: &PictureStruct{
DisableGravatar: config.Bool(false, config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}, "picture.disable_gravatar"), DisableGravatar: config.ValueJSON[bool]("picture.disable_gravatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}),
EnableFederatedAvatar: config.Bool(false, config.CfgSecKey{Sec: "picture", Key: "ENABLE_FEDERATED_AVATAR"}, "picture.enable_federated_avatar"), EnableFederatedAvatar: config.ValueJSON[bool]("picture.enable_federated_avatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "ENABLE_FEDERATED_AVATAR"}),
},
Repository: &RepositoryStruct{
OpenWithEditorApps: config.ValueJSON[OpenWithEditorAppsType]("repository.open-with.editor-apps"),
}, },
} }
} }
@@ -42,6 +82,9 @@ func Config() *ConfigStruct {
type cfgSecKeyGetter struct{} type cfgSecKeyGetter struct{}
func (c cfgSecKeyGetter) GetValue(sec, key string) (v string, has bool) { func (c cfgSecKeyGetter) GetValue(sec, key string) (v string, has bool) {
if key == "" {
return "", false
}
cfgSec, err := CfgProvider.GetSection(sec) cfgSec, err := CfgProvider.GetSection(sec)
if err != nil { if err != nil {
log.Error("Unable to get config section: %q", sec) log.Error("Unable to get config section: %q", sec)
+24 -11
View File
@@ -5,8 +5,11 @@ package config
import ( import (
"context" "context"
"strconv"
"sync" "sync"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
) )
type CfgSecKey struct { type CfgSecKey struct {
@@ -23,14 +26,14 @@ type Value[T any] struct {
revision int revision int
} }
func (value *Value[T]) parse(s string) (v T) { func (value *Value[T]) parse(key, valStr string) (v T) {
switch any(v).(type) { v = value.def
case bool: if valStr != "" {
b, _ := strconv.ParseBool(s) if err := json.Unmarshal(util.UnsafeStringToBytes(valStr), &v); err != nil {
return any(b).(T) log.Error("Unable to unmarshal json config for key %q, err: %v", key, err)
default: }
panic("unsupported config type, please complete the code")
} }
return v
} }
func (value *Value[T]) Value(ctx context.Context) (v T) { func (value *Value[T]) Value(ctx context.Context) (v T) {
@@ -62,7 +65,7 @@ func (value *Value[T]) Value(ctx context.Context) (v T) {
if valStr == nil { if valStr == nil {
v = value.def v = value.def
} else { } else {
v = value.parse(*valStr) v = value.parse(value.dynKey, *valStr)
} }
value.mu.Lock() value.mu.Lock()
@@ -76,6 +79,16 @@ func (value *Value[T]) DynKey() string {
return value.dynKey return value.dynKey
} }
func Bool(def bool, cfgSecKey CfgSecKey, dynKey string) *Value[bool] { func (value *Value[T]) WithDefault(def T) *Value[T] {
return &Value[bool]{def: def, cfgSecKey: cfgSecKey, dynKey: dynKey} value.def = def
return value
}
func (value *Value[T]) WithFileConfig(cfgSecKey CfgSecKey) *Value[T] {
value.cfgSecKey = cfgSecKey
return value
}
func ValueJSON[T any](dynKey string) *Value[T] {
return &Value[T]{dynKey: dynKey}
} }
+22 -20
View File
@@ -25,26 +25,27 @@ var (
// Database holds the database settings // Database holds the database settings
Database = struct { Database = struct {
Type DatabaseType Type DatabaseType
Host string Host string
Name string Name string
User string User string
Passwd string Passwd string
Schema string Schema string
SSLMode string SSLMode string
Path string Path string
LogSQL bool LogSQL bool
MysqlCharset string MysqlCharset string
CharsetCollation string CharsetCollation string
Timeout int // seconds Timeout int // seconds
SQLiteJournalMode string SQLiteJournalMode string
DBConnectRetries int DBConnectRetries int
DBConnectBackoff time.Duration DBConnectBackoff time.Duration
MaxIdleConns int MaxIdleConns int
MaxOpenConns int MaxOpenConns int
ConnMaxLifetime time.Duration ConnMaxLifetime time.Duration
IterateBufferSize int IterateBufferSize int
AutoMigration bool AutoMigration bool
SlowQueryThreshold time.Duration
}{ }{
Timeout: 500, Timeout: 500,
IterateBufferSize: 50, IterateBufferSize: 50,
@@ -87,6 +88,7 @@ func loadDBSetting(rootCfg ConfigProvider) {
Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10) Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10)
Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second) Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second)
Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true) Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true)
Database.SlowQueryThreshold = sec.Key("SLOW_QUERY_THRESHOLD").MustDuration(5 * time.Second)
} }
// DBConnStr returns database connection string // DBConnStr returns database connection string
+24 -17
View File
@@ -9,12 +9,12 @@ import (
"html" "html"
"html/template" "html/template"
"net/url" "net/url"
"slices"
"strings" "strings"
"time" "time"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/svg" "code.gitea.io/gitea/modules/svg"
@@ -35,8 +35,9 @@ func NewFuncMap() template.FuncMap {
// html/template related functions // html/template related functions
"dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names. "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
"Eval": Eval, "Eval": Eval,
"Safe": Safe, "SafeHTML": SafeHTML,
"Escape": Escape, "HTMLFormat": HTMLFormat,
"HTMLEscape": HTMLEscape,
"QueryEscape": url.QueryEscape, "QueryEscape": url.QueryEscape,
"JSEscape": JSEscapeSafe, "JSEscape": JSEscapeSafe,
"Str2html": Str2html, // TODO: rename it to SanitizeHTML "Str2html": Str2html, // TODO: rename it to SanitizeHTML
@@ -159,7 +160,6 @@ func NewFuncMap() template.FuncMap {
"RenderCodeBlock": RenderCodeBlock, "RenderCodeBlock": RenderCodeBlock,
"RenderIssueTitle": RenderIssueTitle, "RenderIssueTitle": RenderIssueTitle,
"RenderEmoji": RenderEmoji, "RenderEmoji": RenderEmoji,
"RenderEmojiPlain": RenderEmojiPlain,
"ReactionToEmoji": ReactionToEmoji, "ReactionToEmoji": ReactionToEmoji,
"RenderMarkdownToHtml": RenderMarkdownToHtml, "RenderMarkdownToHtml": RenderMarkdownToHtml,
@@ -179,8 +179,25 @@ func NewFuncMap() template.FuncMap {
} }
} }
// Safe render raw as HTML func HTMLFormat(s string, rawArgs ...any) template.HTML {
func Safe(s any) template.HTML { args := slices.Clone(rawArgs)
for i, v := range args {
switch v := v.(type) {
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
// for most basic types (including template.HTML which is safe), just do nothing and use it
case string:
args[i] = template.HTMLEscapeString(v)
case fmt.Stringer:
args[i] = template.HTMLEscapeString(v.String())
default:
args[i] = template.HTMLEscapeString(fmt.Sprint(v))
}
}
return template.HTML(fmt.Sprintf(s, args...))
}
// SafeHTML render raw as HTML
func SafeHTML(s any) template.HTML {
switch v := s.(type) { switch v := s.(type) {
case string: case string:
return template.HTML(v) return template.HTML(v)
@@ -201,7 +218,7 @@ func Str2html(s any) template.HTML {
panic(fmt.Sprintf("unexpected type %T", s)) panic(fmt.Sprintf("unexpected type %T", s))
} }
func Escape(s any) template.HTML { func HTMLEscape(s any) template.HTML {
switch v := s.(type) { switch v := s.(type) {
case string: case string:
return template.HTML(html.EscapeString(v)) return template.HTML(html.EscapeString(v))
@@ -215,16 +232,6 @@ func JSEscapeSafe(s string) template.HTML {
return template.HTML(template.JSEscapeString(s)) return template.HTML(template.JSEscapeString(s))
} }
func RenderEmojiPlain(s any) any {
switch v := s.(type) {
case string:
return emoji.ReplaceAliases(v)
case template.HTML:
return template.HTML(emoji.ReplaceAliases(string(v)))
}
panic(fmt.Sprintf("unexpected type %T", s))
}
// DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls // DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls
func DotEscape(raw string) string { func DotEscape(raw string) string {
return strings.ReplaceAll(raw, ".", "\u200d.\u200d") return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
+5
View File
@@ -4,6 +4,7 @@
package templates package templates
import ( import (
"html/template"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -56,3 +57,7 @@ func TestSubjectBodySeparator(t *testing.T) {
func TestJSEscapeSafe(t *testing.T) { func TestJSEscapeSafe(t *testing.T) {
assert.EqualValues(t, `\u0026\u003C\u003E\'\"`, JSEscapeSafe(`&<>'"`)) assert.EqualValues(t, `\u0026\u003C\u003E\'\"`, JSEscapeSafe(`&<>'"`))
} }
func TestHTMLFormat(t *testing.T) {
assert.Equal(t, template.HTML("<a>&lt; < 1</a>"), HTMLFormat("<a>%s %s %d</a>", "<", template.HTML("<"), 1))
}
+8 -2
View File
@@ -44,11 +44,17 @@ func buildSubjectBodyTemplate(stpl *texttmpl.Template, btpl *template.Template,
} }
if _, err := stpl.New(name). if _, err := stpl.New(name).
Parse(string(subjectContent)); err != nil { Parse(string(subjectContent)); err != nil {
log.Warn("Failed to parse template [%s/subject]: %v", name, err) log.Error("Failed to parse template [%s/subject]: %v", name, err)
if !setting.IsProd {
log.Fatal("Please fix the mail template error")
}
} }
if _, err := btpl.New(name). if _, err := btpl.New(name).
Parse(string(bodyContent)); err != nil { Parse(string(bodyContent)); err != nil {
log.Warn("Failed to parse template [%s/body]: %v", name, err) log.Error("Failed to parse template [%s/body]: %v", name, err)
if !setting.IsProd {
log.Fatal("Please fix the mail template error")
}
} }
} }
@@ -149,6 +149,7 @@ func (b *FileBackedBuffer) Close() error {
if b.file != nil { if b.file != nil {
err := b.file.Close() err := b.file.Close()
os.Remove(b.file.Name()) os.Remove(b.file.Name())
b.file = nil
return err return err
} }
return nil return nil
+1 -2
View File
@@ -7,10 +7,9 @@ import (
"crypto" "crypto"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"github.com/minio/sha256-simd"
) )
// GenerateKeyPair generates a public and private keypair // GenerateKeyPair generates a public and private keypair
+1 -1
View File
@@ -7,12 +7,12 @@ import (
"crypto" "crypto"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"regexp" "regexp"
"testing" "testing"
"github.com/minio/sha256-simd"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
+18
View File
@@ -11,6 +11,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"code.gitea.io/gitea/modules/optional"
"golang.org/x/text/cases" "golang.org/x/text/cases"
"golang.org/x/text/language" "golang.org/x/text/language"
) )
@@ -42,6 +44,22 @@ func (o OptionalBool) IsNone() bool {
return o == OptionalBoolNone return o == OptionalBoolNone
} }
// ToGeneric converts OptionalBool to optional.Option[bool]
func (o OptionalBool) ToGeneric() optional.Option[bool] {
if o.IsNone() {
return optional.None[bool]()
}
return optional.Some[bool](o.IsTrue())
}
// OptionalBoolFromGeneric converts optional.Option[bool] to OptionalBool
func OptionalBoolFromGeneric(o optional.Option[bool]) OptionalBool {
if o.Has() {
return OptionalBoolOf(o.Value())
}
return OptionalBoolNone
}
// OptionalBoolOf get the corresponding OptionalBool of a bool // OptionalBoolOf get the corresponding OptionalBool of a bool
func OptionalBoolOf(b bool) OptionalBool { func OptionalBoolOf(b bool) OptionalBool {
if b { if b {
+11 -2
View File
@@ -247,6 +247,7 @@ email_title = Email Settings
smtp_addr = SMTP Host smtp_addr = SMTP Host
smtp_port = SMTP Port smtp_port = SMTP Port
smtp_from = Send Email As smtp_from = Send Email As
smtp_from_invalid = The "Send Email As" address is invalid
smtp_from_helper = Email address Gitea will use. Enter a plain email address or use the "Name" <email@example.com> format. smtp_from_helper = Email address Gitea will use. Enter a plain email address or use the "Name" <email@example.com> format.
mailer_user = SMTP Username mailer_user = SMTP Username
mailer_password = SMTP Password mailer_password = SMTP Password
@@ -367,7 +368,7 @@ forgot_password_title= Forgot Password
forgot_password = Forgot password? forgot_password = Forgot password?
sign_up_now = Need an account? Register now. sign_up_now = Need an account? Register now.
sign_up_successful = Account was successfully created. Welcome! sign_up_successful = Account was successfully created. Welcome!
confirmation_mail_sent_prompt = A new confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the registration process. confirmation_mail_sent_prompt_ex = A new confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the registration process. If your registration email address is incorrect, you can sign in again and change it.
must_change_password = Update your password must_change_password = Update your password
allow_password_change = Require user to change password (recommended) allow_password_change = Require user to change password (recommended)
reset_password_mail_sent_prompt = A confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the account recovery process. reset_password_mail_sent_prompt = A confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the account recovery process.
@@ -377,6 +378,7 @@ prohibit_login = Sign In Prohibited
prohibit_login_desc = Your account is prohibited from signing in, please contact your site administrator. prohibit_login_desc = Your account is prohibited from signing in, please contact your site administrator.
resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again. resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again.
has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (<b>%s</b>). If you haven't received a confirmation email or need to resend a new one, please click on the button below. has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (<b>%s</b>). If you haven't received a confirmation email or need to resend a new one, please click on the button below.
change_unconfirmed_mail_address = If your registration email address is incorrect, you can change it here and resend a new confirmation email.
resend_mail = Click here to resend your activation email resend_mail = Click here to resend your activation email
email_not_associate = The email address is not associated with any account. email_not_associate = The email address is not associated with any account.
send_reset_mail = Send Account Recovery Email send_reset_mail = Send Account Recovery Email
@@ -955,7 +957,7 @@ fork_branch = Branch to be cloned to the fork
all_branches = All branches all_branches = All branches
fork_no_valid_owners = This repository can not be forked because there are no valid owners. fork_no_valid_owners = This repository can not be forked because there are no valid owners.
use_template = Use this template use_template = Use this template
clone_in_vsc = Clone in VS Code open_with_editor = Open with %s
download_zip = Download ZIP download_zip = Download ZIP
download_tar = Download TAR.GZ download_tar = Download TAR.GZ
download_bundle = Download BUNDLE download_bundle = Download BUNDLE
@@ -1914,7 +1916,9 @@ wiki.original_git_entry_tooltip = View original Git file instead of using friend
activity = Activity activity = Activity
activity.navbar.pulse = Pulse activity.navbar.pulse = Pulse
activity.navbar.code_frequency = Code Frequency
activity.navbar.contributors = Contributors activity.navbar.contributors = Contributors
activity.navbar.recent_commits = Recent Commits
activity.period.filter_label = Period: activity.period.filter_label = Period:
activity.period.daily = 1 day activity.period.daily = 1 day
activity.period.halfweekly = 3 days activity.period.halfweekly = 3 days
@@ -2593,7 +2597,9 @@ component_loading = Loading %s...
component_loading_failed = Could not load %s component_loading_failed = Could not load %s
component_loading_info = This might take a bit… component_loading_info = This might take a bit…
component_failed_to_load = An unexpected error happened. component_failed_to_load = An unexpected error happened.
code_frequency.what = code frequency
contributors.what = contributions contributors.what = contributions
recent_commits.what = recent commits
[org] [org]
org_name_holder = Organization Name org_name_holder = Organization Name
@@ -2732,6 +2738,8 @@ integrations = Integrations
authentication = Authentication Sources authentication = Authentication Sources
emails = User Emails emails = User Emails
config = Configuration config = Configuration
config_summary = Summary
config_settings = Settings
notices = System Notices notices = System Notices
monitor = Monitoring monitor = Monitoring
first_page = First first_page = First
@@ -3171,6 +3179,7 @@ config.picture_config = Picture and Avatar Configuration
config.picture_service = Picture Service config.picture_service = Picture Service
config.disable_gravatar = Disable Gravatar config.disable_gravatar = Disable Gravatar
config.enable_federated_avatar = Enable Federated Avatars config.enable_federated_avatar = Enable Federated Avatars
config.open_with_editor_app_help = The "Open with" editors for the clone menu. If left empty, the default will be used. Expand to see the default.
config.git_config = Git Configuration config.git_config = Git Configuration
config.git_disable_diff_highlight = Disable Diff Syntax Highlight config.git_disable_diff_highlight = Disable Diff Syntax Highlight
+35 -2
View File
@@ -123,6 +123,7 @@ pin=Épingler
unpin=Désépingler unpin=Désépingler
artifacts=Artefacts artifacts=Artefacts
confirm_delete_artifact=Êtes-vous sûr de vouloir supprimer lartefact « %s » ?
archived=Archivé archived=Archivé
@@ -361,6 +362,7 @@ disable_register_prompt=Les inscriptions sont désactivées. Veuillez contacter
disable_register_mail=La confirmation par courriel à linscription est désactivée. disable_register_mail=La confirmation par courriel à linscription est désactivée.
manual_activation_only=Contactez l'administrateur de votre site pour terminer l'activation. manual_activation_only=Contactez l'administrateur de votre site pour terminer l'activation.
remember_me=Mémoriser cet appareil remember_me=Mémoriser cet appareil
remember_me.compromised=Le jeton de connexion nest plus valide, ce qui peut indiquer un compte compromis. Veuillez inspecter les activités inhabituelles de votre compte.
forgot_password_title=Mot de passe oublié forgot_password_title=Mot de passe oublié
forgot_password=Mot de passe oublié ? forgot_password=Mot de passe oublié ?
sign_up_now=Pas de compte ? Inscrivez-vous maintenant. sign_up_now=Pas de compte ? Inscrivez-vous maintenant.
@@ -587,6 +589,7 @@ org_still_own_packages=Cette organisation possède encore un ou plusieurs paquet
target_branch_not_exist=La branche cible n'existe pas. target_branch_not_exist=La branche cible n'existe pas.
admin_cannot_delete_self=Vous ne pouvez pas vous supprimer vous-même lorsque vous êtes admin. Veuillez dabord supprimer vos privilèges dadministrateur.
[user] [user]
change_avatar=Changer votre avatar… change_avatar=Changer votre avatar…
@@ -794,7 +797,7 @@ valid_until_date=Valable jusqu'au %s
valid_forever=Valide pour toujours valid_forever=Valide pour toujours
last_used=Dernière utilisation le last_used=Dernière utilisation le
no_activity=Aucune activité récente no_activity=Aucune activité récente
can_read_info=Lue(s) can_read_info=Lecture
can_write_info=Écriture can_write_info=Écriture
key_state_desc=Cette clé a été utilisée au cours des 7 derniers jours key_state_desc=Cette clé a été utilisée au cours des 7 derniers jours
token_state_desc=Ce jeton a été utilisé au cours des 7 derniers jours token_state_desc=Ce jeton a été utilisé au cours des 7 derniers jours
@@ -827,7 +830,7 @@ permissions_public_only=Publique uniquement
permissions_access_all=Tout (public, privé et limité) permissions_access_all=Tout (public, privé et limité)
select_permissions=Sélectionner les autorisations select_permissions=Sélectionner les autorisations
permission_no_access=Aucun accès permission_no_access=Aucun accès
permission_read=Lue(s) permission_read=Lecture
permission_write=Lecture et écriture permission_write=Lecture et écriture
access_token_desc=Les autorisations des jetons sélectionnées se limitent aux <a %s>routes API</a> correspondantes. Lisez la <a %s>documentation</a> pour plus dinformations. access_token_desc=Les autorisations des jetons sélectionnées se limitent aux <a %s>routes API</a> correspondantes. Lisez la <a %s>documentation</a> pour plus dinformations.
at_least_one_permission=Vous devez sélectionner au moins une permission pour créer un jeton. at_least_one_permission=Vous devez sélectionner au moins une permission pour créer un jeton.
@@ -984,6 +987,7 @@ mirror_prune=Purger
mirror_prune_desc=Supprimer les références externes obsolètes mirror_prune_desc=Supprimer les références externes obsolètes
mirror_interval=Intervalle de synchronisation (les unités de temps valides sont 'h', 'm' et 's'). 0 pour désactiver la synchronisation automatique. (Intervalle minimum : %s) mirror_interval=Intervalle de synchronisation (les unités de temps valides sont 'h', 'm' et 's'). 0 pour désactiver la synchronisation automatique. (Intervalle minimum : %s)
mirror_interval_invalid=L'intervalle de synchronisation est invalide. mirror_interval_invalid=L'intervalle de synchronisation est invalide.
mirror_sync=synchronisé
mirror_sync_on_commit=Synchroniser quand les révisions sont soumis mirror_sync_on_commit=Synchroniser quand les révisions sont soumis
mirror_address=Cloner depuis une URL mirror_address=Cloner depuis une URL
mirror_address_desc=Insérez tous les identifiants requis dans la section Autorisation. mirror_address_desc=Insérez tous les identifiants requis dans la section Autorisation.
@@ -1034,6 +1038,7 @@ desc.public=Publique
desc.template=Modèle desc.template=Modèle
desc.internal=Interne desc.internal=Interne
desc.archived=Archivé desc.archived=Archivé
desc.sha256=SHA256
template.items=Élément du modèle template.items=Élément du modèle
template.git_content=Contenu Git (branche par défaut) template.git_content=Contenu Git (branche par défaut)
@@ -1184,6 +1189,8 @@ audio_not_supported_in_browser=Votre navigateur ne supporte pas la balise « au
stored_lfs=Stocké avec Git LFS stored_lfs=Stocké avec Git LFS
symbolic_link=Lien symbolique symbolic_link=Lien symbolique
executable_file=Fichiers exécutables executable_file=Fichiers exécutables
vendored=Externe
generated=Générée
commit_graph=Graphe des révisions commit_graph=Graphe des révisions
commit_graph.select=Sélectionner les branches commit_graph.select=Sélectionner les branches
commit_graph.hide_pr_refs=Masquer les demandes d'ajout commit_graph.hide_pr_refs=Masquer les demandes d'ajout
@@ -1765,6 +1772,7 @@ pulls.merge_pull_request=Créer une révision de fusion
pulls.rebase_merge_pull_request=Rebaser puis avancer rapidement pulls.rebase_merge_pull_request=Rebaser puis avancer rapidement
pulls.rebase_merge_commit_pull_request=Rebaser puis créer une révision de fusion pulls.rebase_merge_commit_pull_request=Rebaser puis créer une révision de fusion
pulls.squash_merge_pull_request=Créer une révision de concaténation pulls.squash_merge_pull_request=Créer une révision de concaténation
pulls.fast_forward_only_merge_pull_request=Avance rapide uniquement
pulls.merge_manually=Fusionner manuellement pulls.merge_manually=Fusionner manuellement
pulls.merge_commit_id=L'ID de la révision de fusion pulls.merge_commit_id=L'ID de la révision de fusion
pulls.require_signed_wont_sign=La branche nécessite des révisions signées mais cette fusion ne sera pas signée pulls.require_signed_wont_sign=La branche nécessite des révisions signées mais cette fusion ne sera pas signée
@@ -1901,6 +1909,7 @@ wiki.page_name_desc=Entrez un nom pour cette page Wiki. Certains noms spéciaux
wiki.original_git_entry_tooltip=Voir le fichier Git original au lieu d'utiliser un lien convivial. wiki.original_git_entry_tooltip=Voir le fichier Git original au lieu d'utiliser un lien convivial.
activity=Activité activity=Activité
activity.navbar.contributors=Contributeurs
activity.period.filter_label=Période : activity.period.filter_label=Période :
activity.period.daily=1 jour activity.period.daily=1 jour
activity.period.halfweekly=3 jours activity.period.halfweekly=3 jours
@@ -1966,7 +1975,10 @@ activity.git_stats_and_deletions=et
activity.git_stats_deletion_1=%d suppression activity.git_stats_deletion_1=%d suppression
activity.git_stats_deletion_n=%d suppressions activity.git_stats_deletion_n=%d suppressions
contributors.contribution_type.filter_label=Type de contribution :
contributors.contribution_type.commits=Révisions contributors.contribution_type.commits=Révisions
contributors.contribution_type.additions=Ajouts
contributors.contribution_type.deletions=Suppressions
search=Chercher search=Chercher
search.search_repo=Rechercher dans le dépôt search.search_repo=Rechercher dans le dépôt
@@ -2314,6 +2326,8 @@ settings.protect_approvals_whitelist_users=Évaluateurs autorisés :
settings.protect_approvals_whitelist_teams=Équipes d’évaluateurs autorisés : settings.protect_approvals_whitelist_teams=Équipes d’évaluateurs autorisés :
settings.dismiss_stale_approvals=Révoquer automatiquement les approbations périmées settings.dismiss_stale_approvals=Révoquer automatiquement les approbations périmées
settings.dismiss_stale_approvals_desc=Lorsque des nouvelles révisions changent le contenu de la demande dajout, les approbations existantes sont révoquées. settings.dismiss_stale_approvals_desc=Lorsque des nouvelles révisions changent le contenu de la demande dajout, les approbations existantes sont révoquées.
settings.ignore_stale_approvals=Ignorer les approbations obsolètes
settings.ignore_stale_approvals_desc=Ignorer les approbations danciennes révisions (évaluations obsolètes) du décompte des approbations de la demande dajout. Non pertinent quand les évaluations obsolètes sont déjà révoquées.
settings.require_signed_commits=Exiger des révisions signées settings.require_signed_commits=Exiger des révisions signées
settings.require_signed_commits_desc=Rejeter les soumissions sur cette branche lorsqu'ils ne sont pas signés ou vérifiables. settings.require_signed_commits_desc=Rejeter les soumissions sur cette branche lorsqu'ils ne sont pas signés ou vérifiables.
settings.protect_branch_name_pattern=Motif de nom de branche protégé settings.protect_branch_name_pattern=Motif de nom de branche protégé
@@ -2369,6 +2383,7 @@ settings.archive.error=Une erreur s'est produite lors de l'archivage du dépôt.
settings.archive.error_ismirror=Vous ne pouvez pas archiver un dépôt en miroir. settings.archive.error_ismirror=Vous ne pouvez pas archiver un dépôt en miroir.
settings.archive.branchsettings_unavailable=Le paramétrage des branches n'est pas disponible quand le dépôt est archivé. settings.archive.branchsettings_unavailable=Le paramétrage des branches n'est pas disponible quand le dépôt est archivé.
settings.archive.tagsettings_unavailable=Le paramétrage des étiquettes n'est pas disponible si le dépôt est archivé. settings.archive.tagsettings_unavailable=Le paramétrage des étiquettes n'est pas disponible si le dépôt est archivé.
settings.archive.mirrors_unavailable=Les miroirs ne sont pas disponibles lorsque le dépôt est archivé.
settings.unarchive.button=Réhabiliter settings.unarchive.button=Réhabiliter
settings.unarchive.header=Réhabiliter ce dépôt settings.unarchive.header=Réhabiliter ce dépôt
settings.unarchive.text=Réhabiliter un dépôt dégèle les actions de révisions et de soumissions, la gestion des tickets et des demandes d'ajouts. settings.unarchive.text=Réhabiliter un dépôt dégèle les actions de révisions et de soumissions, la gestion des tickets et des demandes d'ajouts.
@@ -2568,6 +2583,11 @@ error.csv.unexpected=Impossible de visualiser ce fichier car il contient un cara
error.csv.invalid_field_count=Impossible de visualiser ce fichier car il contient un nombre de champs incorrect à la ligne %d. error.csv.invalid_field_count=Impossible de visualiser ce fichier car il contient un nombre de champs incorrect à la ligne %d.
[graphs] [graphs]
component_loading=Chargement de %s…
component_loading_failed=Impossible de charger %s.
component_loading_info=Ça prend son temps…
component_failed_to_load=Une erreur inattendue sest produite.
contributors.what=contributions
[org] [org]
org_name_holder=Nom de l'organisation org_name_holder=Nom de l'organisation
@@ -2695,6 +2715,7 @@ teams.invite.description=Veuillez cliquer sur le bouton ci-dessous pour rejoindr
[admin] [admin]
dashboard=Tableau de bord dashboard=Tableau de bord
self_check=Autodiagnostique
identity_access=Identité et accès identity_access=Identité et accès
users=Comptes utilisateurs users=Comptes utilisateurs
organizations=Organisations organizations=Organisations
@@ -2740,6 +2761,7 @@ dashboard.delete_missing_repos=Supprimer tous les dépôts dont les fichiers Git
dashboard.delete_missing_repos.started=Tâche de suppression de tous les dépôts sans fichiers Git démarrée. dashboard.delete_missing_repos.started=Tâche de suppression de tous les dépôts sans fichiers Git démarrée.
dashboard.delete_generated_repository_avatars=Supprimer les avatars de dépôt générés dashboard.delete_generated_repository_avatars=Supprimer les avatars de dépôt générés
dashboard.sync_repo_branches=Synchroniser les branches manquantes depuis Git vers la base de donnée. dashboard.sync_repo_branches=Synchroniser les branches manquantes depuis Git vers la base de donnée.
dashboard.sync_repo_tags=Synchroniser les étiquettes git depuis les dépôts vers la base de données
dashboard.update_mirrors=Actualiser les miroirs dashboard.update_mirrors=Actualiser les miroirs
dashboard.repo_health_check=Vérifier l'état de santé de tous les dépôts dashboard.repo_health_check=Vérifier l'état de santé de tous les dépôts
dashboard.check_repo_stats=Voir les statistiques de tous les dépôts dashboard.check_repo_stats=Voir les statistiques de tous les dépôts
@@ -2794,6 +2816,7 @@ dashboard.stop_endless_tasks=Arrêter les tâches sans fin
dashboard.cancel_abandoned_jobs=Annuler les jobs abandonnés dashboard.cancel_abandoned_jobs=Annuler les jobs abandonnés
dashboard.start_schedule_tasks=Démarrer les tâches planifiées dashboard.start_schedule_tasks=Démarrer les tâches planifiées
dashboard.sync_branch.started=Début de la synchronisation des branches dashboard.sync_branch.started=Début de la synchronisation des branches
dashboard.sync_tag.started=Synchronisation des étiquettes
dashboard.rebuild_issue_indexer=Reconstruire lindexeur des tickets dashboard.rebuild_issue_indexer=Reconstruire lindexeur des tickets
users.user_manage_panel=Gestion du compte utilisateur users.user_manage_panel=Gestion du compte utilisateur
@@ -3220,6 +3243,12 @@ notices.desc=Description
notices.op=Opération notices.op=Opération
notices.delete_success=Les informations systèmes ont été supprimées. notices.delete_success=Les informations systèmes ont été supprimées.
self_check.no_problem_found=Aucun problème trouvé pour linstant.
self_check.database_collation_mismatch=Exige que la base de données utilise la collation %s.
self_check.database_collation_case_insensitive=La base de données utilise la collation %s, insensible à la casse. Bien que Gitea soit compatible, il peut y avoir quelques rares cas qui ne fonctionnent pas comme prévu.
self_check.database_inconsistent_collation_columns=La base de données utilise la collation %s, mais ces colonnes utilisent des collations différentes. Cela peut causer des problèmes imprévus.
self_check.database_fix_mysql=Pour les utilisateurs de MySQL ou MariaDB, vous pouvez utiliser la commande « gitea doctor convert » dans un terminal ou exécuter une requête du type « ALTER … COLLATE ... » pour résoudre les problèmes de collation.
self_check.database_fix_mssql=Pour les utilisateurs de MSSQL, vous ne pouvez résoudre le problème quen exécutant une requête SQL du type « ALTER … COLLATE … ».
[action] [action]
create_repo=a créé le dépôt <a href="%s">%s</a> create_repo=a créé le dépôt <a href="%s">%s</a>
@@ -3407,6 +3436,7 @@ rpm.distros.suse=sur les distributions basées sur SUSE
rpm.install=Pour installer le paquet, exécutez la commande suivante : rpm.install=Pour installer le paquet, exécutez la commande suivante :
rpm.repository=Informations sur le Dépôt rpm.repository=Informations sur le Dépôt
rpm.repository.architectures=Architectures rpm.repository.architectures=Architectures
rpm.repository.multiple_groups=Ce paquet est disponible en plusieurs groupes.
rubygems.install=Pour installer le paquet en utilisant gem, exécutez la commande suivante : rubygems.install=Pour installer le paquet en utilisant gem, exécutez la commande suivante :
rubygems.install2=ou ajoutez-le au Gemfile : rubygems.install2=ou ajoutez-le au Gemfile :
rubygems.dependencies.runtime=Dépendances d'exécution rubygems.dependencies.runtime=Dépendances d'exécution
@@ -3539,6 +3569,8 @@ runs.actors_no_select=Tous les acteurs
runs.status_no_select=Touts les statuts runs.status_no_select=Touts les statuts
runs.no_results=Aucun résultat correspondant. runs.no_results=Aucun résultat correspondant.
runs.no_workflows=Il n'y a pas encore de workflows. runs.no_workflows=Il n'y a pas encore de workflows.
runs.no_workflows.quick_start=Vous découvrez les Actions Gitea ? Consultez <a target="_blank" rel="noopener noreferrer" href="%s">le didacticiel</a>.
runs.no_workflows.documentation=Pour plus dinformations sur les actions Gitea, voir <a target="_blank" rel="noopener noreferrer" href="%s">la documentation</a>.
runs.no_runs=Le flux de travail n'a pas encore d'exécution. runs.no_runs=Le flux de travail n'a pas encore d'exécution.
runs.empty_commit_message=(message de révision vide) runs.empty_commit_message=(message de révision vide)
@@ -3557,6 +3589,7 @@ variables.none=Il n'y a pas encore de variables.
variables.deletion=Retirer la variable variables.deletion=Retirer la variable
variables.deletion.description=La suppression dune variable est permanente et ne peut être défaite. Continuer ? variables.deletion.description=La suppression dune variable est permanente et ne peut être défaite. Continuer ?
variables.description=Les variables sont passées aux actions et ne peuvent être lues autrement. variables.description=Les variables sont passées aux actions et ne peuvent être lues autrement.
variables.id_not_exist=La variable avec lID %d nexiste pas.
variables.edit=Modifier la variable variables.edit=Modifier la variable
variables.deletion.failed=Impossible de retirer la variable. variables.deletion.failed=Impossible de retirer la variable.
variables.deletion.success=La variable a bien été retirée. variables.deletion.success=La variable a bien été retirée.

Some files were not shown because too many files have changed in this diff Show More