Merge remote-tracking branch 'origin/main' into feature/heatmap-visibility-options
234
CHANGELOG.md
@ -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
|
||||
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
|
||||
|
||||
* BREAKING
|
||||
|
2
Makefile
@ -969,7 +969,7 @@ generate-gitignore:
|
||||
|
||||
.PHONY: generate-images
|
||||
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)
|
||||
|
||||
.PHONY: generate-manpage
|
||||
|
@ -1,20 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
import imageminZopfli from 'imagemin-zopfli';
|
||||
import {optimize} from 'svgo';
|
||||
import {fabric} from 'fabric';
|
||||
import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node';
|
||||
import {readFile, writeFile} from 'node:fs/promises';
|
||||
import {argv, exit} from 'node:process';
|
||||
|
||||
function exit(err) {
|
||||
function doExit(err) {
|
||||
if (err) console.error(err);
|
||||
process.exit(err ? 1 : 0);
|
||||
}
|
||||
|
||||
function loadSvg(svg) {
|
||||
return new Promise((resolve) => {
|
||||
fabric.loadSVGFromString(svg, (objects, options) => {
|
||||
resolve({objects, options});
|
||||
});
|
||||
});
|
||||
exit(err ? 1 : 0);
|
||||
}
|
||||
|
||||
async function generate(svg, path, {size, bg}) {
|
||||
@ -35,14 +28,14 @@ async function generate(svg, path, {size, bg}) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {objects, options} = await loadSvg(svg);
|
||||
const canvas = new fabric.Canvas();
|
||||
const {objects, options} = await loadSVGFromString(svg);
|
||||
const canvas = new Canvas();
|
||||
canvas.setDimensions({width: size, height: size});
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1);
|
||||
|
||||
if (bg) {
|
||||
canvas.add(new fabric.Rect({
|
||||
canvas.add(new Rect({
|
||||
left: 0,
|
||||
top: 0,
|
||||
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();
|
||||
|
||||
let png = Buffer.from([]);
|
||||
@ -64,7 +57,7 @@ async function generate(svg, path, {size, bg}) {
|
||||
}
|
||||
|
||||
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 faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8');
|
||||
|
||||
@ -80,7 +73,7 @@ async function main() {
|
||||
}
|
||||
|
||||
try {
|
||||
exit(await main());
|
||||
doExit(await main());
|
||||
} catch (err) {
|
||||
exit(err);
|
||||
doExit(err);
|
||||
}
|
||||
|
@ -4,15 +4,16 @@ import {optimize} from 'svgo';
|
||||
import {parse} from 'node:path';
|
||||
import {readFile, writeFile, mkdir} from 'node:fs/promises';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {exit} from 'node:process';
|
||||
|
||||
const glob = (pattern) => fastGlob.sync(pattern, {
|
||||
cwd: fileURLToPath(new URL('..', import.meta.url)),
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
function exit(err) {
|
||||
function doExit(err) {
|
||||
if (err) console.error(err);
|
||||
process.exit(err ? 1 : 0);
|
||||
exit(err ? 1 : 0);
|
||||
}
|
||||
|
||||
async function processFile(file, {prefix, fullName} = {}) {
|
||||
@ -64,7 +65,7 @@ async function main() {
|
||||
}
|
||||
|
||||
try {
|
||||
exit(await main());
|
||||
doExit(await main());
|
||||
} catch (err) {
|
||||
exit(err);
|
||||
doExit(err);
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ import (
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
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/util"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@ -123,10 +123,10 @@ func runCreateUser(c *cli.Context) error {
|
||||
changePassword = c.Bool("must-change-password")
|
||||
}
|
||||
|
||||
restricted := util.OptionalBoolNone
|
||||
restricted := optional.None[bool]()
|
||||
|
||||
if c.IsSet("restricted") {
|
||||
restricted = util.OptionalBoolOf(c.Bool("restricted"))
|
||||
restricted = optional.Some(c.Bool("restricted"))
|
||||
}
|
||||
|
||||
// default user visibility in app.ini
|
||||
@ -142,7 +142,7 @@ func runCreateUser(c *cli.Context) error {
|
||||
}
|
||||
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsActive: optional.Some(true),
|
||||
IsRestricted: restricted,
|
||||
}
|
||||
|
||||
|
@ -412,6 +412,10 @@ USER = root
|
||||
;;
|
||||
;; Whether execute database models migrations automatically
|
||||
;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_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 =
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -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`.
|
||||
- `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.
|
||||
- `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.
|
||||
|
||||
@ -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
|
||||
- `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`)
|
||||
|
||||
|
@ -497,6 +497,8 @@ Gitea 创建以下非唯一队列:
|
||||
|
||||
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**:用户电子邮件通知的默认配置(用户可配置)。选项:enabled、onmention、disabled
|
||||
- `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。
|
||||
- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion`, 未来可以增加更多设置。
|
||||
- `deletion`: 用户不能通过界面或者API删除他自己。
|
||||
|
||||
## 安全性 (`security`)
|
||||
|
||||
|
@ -11,10 +11,13 @@ import (
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/contexts"
|
||||
"xorm.io/xorm/names"
|
||||
"xorm.io/xorm/schemas"
|
||||
|
||||
@ -143,6 +146,13 @@ func InitEngine(ctx context.Context) error {
|
||||
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
|
||||
xormEngine.SetDefaultContext(ctx)
|
||||
|
||||
if setting.Database.SlowQueryThreshold > 0 {
|
||||
xormEngine.AddHook(&SlowQueryHook{
|
||||
Threshold: setting.Database.SlowQueryThreshold,
|
||||
Logger: log.GetLogger("xorm"),
|
||||
})
|
||||
}
|
||||
|
||||
SetDefaultEngine(ctx, xormEngine)
|
||||
return nil
|
||||
}
|
||||
@ -298,3 +308,24 @@ func SetLogSQL(ctx context.Context, on bool) {
|
||||
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
|
||||
}
|
||||
|
@ -135,3 +135,27 @@
|
||||
user_id: 31
|
||||
repo_id: 28
|
||||
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
|
||||
|
@ -45,3 +45,9 @@
|
||||
repo_id: 22
|
||||
user_id: 18
|
||||
mode: 2 # write
|
||||
|
||||
-
|
||||
id: 9
|
||||
repo_id: 60
|
||||
user_id: 38
|
||||
mode: 2 # write
|
||||
|
@ -293,3 +293,27 @@
|
||||
lower_email: user37@example.com
|
||||
is_activated: 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
|
||||
|
@ -338,3 +338,37 @@
|
||||
created_unix: 978307210
|
||||
updated_unix: 978307210
|
||||
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
|
||||
|
@ -99,3 +99,21 @@
|
||||
uid: 5
|
||||
org_id: 36
|
||||
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
|
||||
|
@ -9,6 +9,7 @@
|
||||
head_branch: branch1
|
||||
base_branch: master
|
||||
merge_base: 4a357436d925b5c974181ff12a994538ddc5a269
|
||||
merged_commit_id: 1a8823cd1a9549fde083f992f6b9b87a7ab74fb3
|
||||
has_merged: true
|
||||
merger_id: 2
|
||||
|
||||
@ -98,3 +99,21 @@
|
||||
index: 1
|
||||
head_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
|
||||
|
@ -676,3 +676,45 @@
|
||||
type: 1
|
||||
config: "{}"
|
||||
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
|
||||
|
@ -1706,3 +1706,65 @@
|
||||
is_private: true
|
||||
status: 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
|
||||
|
@ -217,3 +217,25 @@
|
||||
num_members: 1
|
||||
includes_all_repositories: false
|
||||
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
|
||||
|
@ -63,3 +63,15 @@
|
||||
org_id: 17
|
||||
team_id: 9
|
||||
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
|
||||
|
@ -286,3 +286,39 @@
|
||||
team_id: 2
|
||||
type: 8
|
||||
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
|
||||
|
@ -129,3 +129,21 @@
|
||||
org_id: 17
|
||||
team_id: 9
|
||||
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
|
||||
|
@ -1369,3 +1369,151 @@
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
actions_visibility: 0
|
||||
|
||||
-
|
||||
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: ""
|
||||
actions_visibility: 0
|
||||
|
||||
-
|
||||
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: ""
|
||||
actions_visibility: 0
|
||||
|
||||
-
|
||||
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: ""
|
||||
actions_visibility: 0
|
||||
|
||||
-
|
||||
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: ""
|
||||
actions_visibility: 0
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@ -67,7 +67,7 @@ type FindBranchOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
ExcludeBranchNames []string
|
||||
IsDeletedBranch util.OptionalBool
|
||||
IsDeletedBranch optional.Option[bool]
|
||||
OrderBy string
|
||||
Keyword string
|
||||
}
|
||||
@ -81,8 +81,8 @@ func (opts FindBranchOptions) ToConds() builder.Cond {
|
||||
if len(opts.ExcludeBranchNames) > 0 {
|
||||
cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames))
|
||||
}
|
||||
if !opts.IsDeletedBranch.IsNone() {
|
||||
cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.IsTrue()})
|
||||
if opts.IsDeletedBranch.Has() {
|
||||
cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.Value()})
|
||||
}
|
||||
if 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 {
|
||||
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 != "" {
|
||||
orderBy += ", "
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"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{
|
||||
ListOptions: db.ListOptionsAll,
|
||||
RepoID: repo.ID,
|
||||
IsDeletedBranch: util.OptionalBoolTrue,
|
||||
IsDeletedBranch: optional.Some(true),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, branches, 2)
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
@ -56,7 +56,7 @@ func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string)
|
||||
Page: page,
|
||||
},
|
||||
RepoID: repoID,
|
||||
IsDeletedBranch: util.OptionalBoolFalse,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -379,7 +379,7 @@ func TestCountIssues(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 20, count)
|
||||
assert.EqualValues(t, 22, count)
|
||||
}
|
||||
|
||||
func TestIssueLoadAttributes(t *testing.T) {
|
||||
|
@ -1093,3 +1093,23 @@ func InsertPullRequests(ctx context.Context, prs ...*PullRequest) error {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -339,6 +339,18 @@ func TestGetApprovers(t *testing.T) {
|
||||
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) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
reponame := "repo1"
|
||||
|
@ -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
|
||||
// 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) {
|
||||
if user.IsOrganization() {
|
||||
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 {
|
||||
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
|
||||
|
@ -138,12 +138,12 @@ func getTestCases() []struct {
|
||||
{
|
||||
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
||||
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",
|
||||
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",
|
||||
@ -158,7 +158,7 @@ func getTestCases() []struct {
|
||||
{
|
||||
name: "AllPublic/PublicRepositoriesOfOrganization",
|
||||
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",
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
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").
|
||||
Join("INNER", "team_repo", "`team_repo`.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").
|
||||
Select("`team_user`.uid").
|
||||
Find(&additionalUserIDs); err != nil {
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"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
|
||||
type CreateUserOverwriteOptions struct {
|
||||
KeepEmailPrivate util.OptionalBool
|
||||
KeepEmailPrivate optional.Option[bool]
|
||||
Visibility *structs.VisibleType
|
||||
AllowCreateOrganization util.OptionalBool
|
||||
AllowCreateOrganization optional.Option[bool]
|
||||
EmailNotificationsPreference *string
|
||||
MaxRepoCreation *int
|
||||
Theme *string
|
||||
IsRestricted util.OptionalBool
|
||||
IsActive util.OptionalBool
|
||||
IsRestricted optional.Option[bool]
|
||||
IsActive optional.Option[bool]
|
||||
}
|
||||
|
||||
// CreateUser creates record of a new user.
|
||||
@ -607,14 +608,14 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
|
||||
// overwrite defaults if set
|
||||
if len(overwriteDefault) != 0 && overwriteDefault[0] != nil {
|
||||
overwrite := overwriteDefault[0]
|
||||
if !overwrite.KeepEmailPrivate.IsNone() {
|
||||
u.KeepEmailPrivate = overwrite.KeepEmailPrivate.IsTrue()
|
||||
if overwrite.KeepEmailPrivate.Has() {
|
||||
u.KeepEmailPrivate = overwrite.KeepEmailPrivate.Value()
|
||||
}
|
||||
if overwrite.Visibility != nil {
|
||||
u.Visibility = *overwrite.Visibility
|
||||
}
|
||||
if !overwrite.AllowCreateOrganization.IsNone() {
|
||||
u.AllowCreateOrganization = overwrite.AllowCreateOrganization.IsTrue()
|
||||
if overwrite.AllowCreateOrganization.Has() {
|
||||
u.AllowCreateOrganization = overwrite.AllowCreateOrganization.Value()
|
||||
}
|
||||
if overwrite.EmailNotificationsPreference != nil {
|
||||
u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference
|
||||
@ -625,11 +626,11 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
|
||||
if overwrite.Theme != nil {
|
||||
u.Theme = *overwrite.Theme
|
||||
}
|
||||
if !overwrite.IsRestricted.IsNone() {
|
||||
u.IsRestricted = overwrite.IsRestricted.IsTrue()
|
||||
if overwrite.IsRestricted.Has() {
|
||||
u.IsRestricted = overwrite.IsRestricted.Value()
|
||||
}
|
||||
if !overwrite.IsActive.IsNone() {
|
||||
u.IsActive = overwrite.IsActive.IsTrue()
|
||||
if overwrite.IsActive.Has() {
|
||||
u.IsActive = overwrite.IsActive.Value()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ func TestSearchUsers(t *testing.T) {
|
||||
[]int64{19, 25})
|
||||
|
||||
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}},
|
||||
[]int64{})
|
||||
@ -101,13 +101,13 @@ func TestSearchUsers(t *testing.T) {
|
||||
}
|
||||
|
||||
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},
|
||||
[]int64{9})
|
||||
|
||||
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},
|
||||
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
|
||||
|
@ -307,12 +307,6 @@ func RepoRefForAPI(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ref := ctx.FormTrim("ref"); len(ref) > 0 {
|
||||
commit, err := ctx.Repo.GitRepo.GetCommit(ref)
|
||||
if err != nil {
|
||||
@ -331,6 +325,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny)
|
||||
var err error
|
||||
|
||||
if ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
|
||||
@ -346,7 +341,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if len(refName) == objectFormat.FullLength() {
|
||||
} else if len(refName) == ctx.Repo.GetObjectFormat().FullLength() {
|
||||
ctx.Repo.CommitID = refName
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
|
@ -192,6 +192,7 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
|
||||
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
ctx.Data["SystemConfig"] = setting.Config()
|
||||
ctx.Data["CsrfToken"] = ctx.Csrf.GetToken()
|
||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
|
||||
|
||||
|
@ -11,6 +11,8 @@ import (
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
@ -255,6 +257,19 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
||||
ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects)
|
||||
ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages)
|
||||
ctx.Data["CanReadCode"] = ctx.Org.CanReadUnit(ctx, unit.TypeCode)
|
||||
|
||||
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
|
||||
if len(ctx.ContextUser.Description) != 0 {
|
||||
content, err := markdown.RenderString(&markup.RenderContext{
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
Ctx: ctx,
|
||||
}, ctx.ContextUser.Description)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["RenderedDescription"] = content
|
||||
}
|
||||
}
|
||||
|
||||
// OrgAssignment returns a middleware to handle organization assignment
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@ -82,6 +83,10 @@ func (r *Repository) CanCreateBranch() bool {
|
||||
return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanCreateBranch()
|
||||
}
|
||||
|
||||
func (r *Repository) GetObjectFormat() git.ObjectFormat {
|
||||
return git.ObjectFormatFromName(r.Repository.ObjectFormatName)
|
||||
}
|
||||
|
||||
// RepoMustNotBeArchived checks if a repo is archived
|
||||
func RepoMustNotBeArchived() func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
@ -671,7 +676,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
|
||||
branchOpts := git_model.FindBranchOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
IsDeletedBranch: util.OptionalBoolFalse,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
ListOptions: db.ListOptionsAll,
|
||||
}
|
||||
branchesTotal, err := db.Count[git_model.Branch](ctx, branchOpts)
|
||||
@ -829,9 +834,8 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
|
||||
}
|
||||
// For legacy and API support only full commit sha
|
||||
parts := strings.Split(path, "/")
|
||||
objectFormat, _ := repo.GitRepo.GetObjectFormat()
|
||||
|
||||
if len(parts) > 0 && len(parts[0]) == objectFormat.FullLength() {
|
||||
if len(parts) > 0 && len(parts[0]) == git.ObjectFormatFromName(repo.Repository.ObjectFormatName).FullLength() {
|
||||
repo.TreePath = strings.Join(parts[1:], "/")
|
||||
return parts[0]
|
||||
}
|
||||
@ -875,9 +879,8 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
|
||||
return getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsTagExist)
|
||||
case RepoRefCommit:
|
||||
parts := strings.Split(path, "/")
|
||||
objectFormat, _ := repo.GitRepo.GetObjectFormat()
|
||||
|
||||
if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= objectFormat.FullLength() {
|
||||
if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= repo.GetObjectFormat().FullLength() {
|
||||
repo.TreePath = strings.Join(parts[1:], "/")
|
||||
return parts[0]
|
||||
}
|
||||
@ -936,12 +939,6 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
||||
}
|
||||
}
|
||||
|
||||
objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
|
||||
if err != nil {
|
||||
log.Error("Cannot determine objectFormat for repository: %w", err)
|
||||
ctx.Repo.Repository.MarkAsBrokenEmpty()
|
||||
}
|
||||
|
||||
// Get default branch.
|
||||
if len(ctx.Params("*")) == 0 {
|
||||
refName = ctx.Repo.Repository.DefaultBranch
|
||||
@ -1008,7 +1005,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
||||
return cancel
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if len(refName) >= 7 && len(refName) <= objectFormat.FullLength() {
|
||||
} else if len(refName) >= 7 && len(refName) <= ctx.Repo.GetObjectFormat().FullLength() {
|
||||
ctx.Repo.IsViewCommit = true
|
||||
ctx.Repo.CommitID = refName
|
||||
|
||||
@ -1018,7 +1015,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
||||
return cancel
|
||||
}
|
||||
// If short commit ID add canonical link header
|
||||
if len(refName) < objectFormat.FullLength() {
|
||||
if len(refName) < ctx.Repo.GetObjectFormat().FullLength() {
|
||||
ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
|
||||
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
|
||||
}
|
||||
|
35
modules/git/attribute.go
Normal 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]()
|
||||
}
|
@ -102,7 +102,17 @@ func (b *blobReader) Read(p []byte) (n int, err error) {
|
||||
|
||||
// Close implements io.Closer
|
||||
func (b *blobReader) Close() error {
|
||||
if b.rd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -291,7 +291,14 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe
|
||||
}
|
||||
|
||||
checker := &CheckAttributeReader{
|
||||
Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"},
|
||||
Attributes: []string{
|
||||
AttributeLinguistVendored,
|
||||
AttributeLinguistGenerated,
|
||||
AttributeLinguistDocumentation,
|
||||
AttributeLinguistDetectable,
|
||||
AttributeLinguistLanguage,
|
||||
AttributeGitlabLanguage,
|
||||
},
|
||||
Repo: repo,
|
||||
IndexFile: indexFilename,
|
||||
WorkTree: worktree,
|
||||
|
@ -24,7 +24,7 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
|
||||
select {
|
||||
case attr := <-wr.ReadAttribute():
|
||||
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)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
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 {
|
||||
case attr := <-wr.ReadAttribute():
|
||||
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)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
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.EqualValues(t, attributeTriple{
|
||||
Filename: "shouldbe.vendor",
|
||||
Attribute: "linguist-vendored",
|
||||
Attribute: AttributeLinguistVendored,
|
||||
Value: "set",
|
||||
}, attr)
|
||||
attr = <-wr.ReadAttribute()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, attributeTriple{
|
||||
Filename: "shouldbe.vendor",
|
||||
Attribute: "linguist-generated",
|
||||
Attribute: AttributeLinguistGenerated,
|
||||
Value: "unspecified",
|
||||
}, attr)
|
||||
attr = <-wr.ReadAttribute()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, attributeTriple{
|
||||
Filename: "shouldbe.vendor",
|
||||
Attribute: "linguist-language",
|
||||
Attribute: AttributeLinguistLanguage,
|
||||
Value: "unspecified",
|
||||
}, attr)
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ package git
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -46,3 +48,20 @@ func mergeLanguageStats(stats map[string]int64) map[string]int64 {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ package git
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/analyze"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"github.com/go-enry/go-enry/v2"
|
||||
"github.com/go-git/go-git/v5"
|
||||
@ -57,40 +57,38 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
|
||||
return nil
|
||||
}
|
||||
|
||||
notVendored := false
|
||||
notGenerated := false
|
||||
isVendored := optional.None[bool]()
|
||||
isGenerated := optional.None[bool]()
|
||||
isDocumentation := optional.None[bool]()
|
||||
isDetectable := optional.None[bool]()
|
||||
|
||||
if checker != nil {
|
||||
attrs, err := checker.CheckPath(f.Name)
|
||||
if err == nil {
|
||||
if vendored, has := attrs["linguist-vendored"]; has {
|
||||
if vendored == "set" || vendored == "true" {
|
||||
isVendored = AttributeToBool(attrs, AttributeLinguistVendored)
|
||||
if isVendored.ValueOrDefault(false) {
|
||||
return nil
|
||||
}
|
||||
notVendored = vendored == "false"
|
||||
}
|
||||
if generated, has := attrs["linguist-generated"]; has {
|
||||
if generated == "set" || generated == "true" {
|
||||
return nil
|
||||
}
|
||||
notGenerated = generated == "false"
|
||||
}
|
||||
if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" {
|
||||
// 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
|
||||
isGenerated = AttributeToBool(attrs, AttributeLinguistGenerated)
|
||||
if isGenerated.ValueOrDefault(false) {
|
||||
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 {
|
||||
|
||||
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 := enry.GetLanguageGroup(language)
|
||||
if len(group) != 0 {
|
||||
@ -103,10 +101,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!notVendored && analyze.IsVendor(f.Name)) || enry.IsDotFile(f.Name) ||
|
||||
enry.IsDocumentation(f.Name) || enry.IsConfiguration(f.Name) {
|
||||
if (!isVendored.Has() && analyze.IsVendor(f.Name)) ||
|
||||
enry.IsDotFile(f.Name) ||
|
||||
(!isDocumentation.Has() && enry.IsDocumentation(f.Name)) ||
|
||||
enry.IsConfiguration(f.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -115,12 +114,10 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
|
||||
if f.Size <= bigFileSize {
|
||||
content, _ = readFile(f, fileSizeLimit)
|
||||
}
|
||||
if !notGenerated && enry.IsGenerated(f.Name, content) {
|
||||
if !isGenerated.Has() && enry.IsGenerated(f.Name, content) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Use .gitattributes file for linguist overrides
|
||||
|
||||
language := analyze.GetCodeLanguage(f.Name, content)
|
||||
if language == enry.OtherLanguage || language == "" {
|
||||
return nil
|
||||
@ -138,7 +135,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
|
||||
included = langtype == enry.Programming || langtype == enry.Markup
|
||||
includedLanguage[language] = included
|
||||
}
|
||||
if included {
|
||||
if included || isDetectable.ValueOrDefault(false) {
|
||||
sizes[language] += f.Size
|
||||
} else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) {
|
||||
firstExcludedLanguage = language
|
||||
|
@ -8,10 +8,10 @@ package git
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/analyze"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"github.com/go-enry/go-enry/v2"
|
||||
)
|
||||
@ -88,40 +88,38 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
|
||||
continue
|
||||
}
|
||||
|
||||
notVendored := false
|
||||
notGenerated := false
|
||||
isVendored := optional.None[bool]()
|
||||
isGenerated := optional.None[bool]()
|
||||
isDocumentation := optional.None[bool]()
|
||||
isDetectable := optional.None[bool]()
|
||||
|
||||
if checker != nil {
|
||||
attrs, err := checker.CheckPath(f.Name())
|
||||
if err == nil {
|
||||
if vendored, has := attrs["linguist-vendored"]; has {
|
||||
if vendored == "set" || vendored == "true" {
|
||||
isVendored = AttributeToBool(attrs, AttributeLinguistVendored)
|
||||
if isVendored.ValueOrDefault(false) {
|
||||
continue
|
||||
}
|
||||
notVendored = vendored == "false"
|
||||
}
|
||||
if generated, has := attrs["linguist-generated"]; has {
|
||||
if generated == "set" || generated == "true" {
|
||||
continue
|
||||
}
|
||||
notGenerated = generated == "false"
|
||||
}
|
||||
if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" {
|
||||
// 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()
|
||||
isGenerated = AttributeToBool(attrs, AttributeLinguistGenerated)
|
||||
if isGenerated.ValueOrDefault(false) {
|
||||
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 {
|
||||
|
||||
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 := enry.GetLanguageGroup(language)
|
||||
if len(group) != 0 {
|
||||
@ -133,12 +131,12 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!notVendored && analyze.IsVendor(f.Name())) || enry.IsDotFile(f.Name()) ||
|
||||
enry.IsDocumentation(f.Name()) || enry.IsConfiguration(f.Name()) {
|
||||
if (!isVendored.Has() && analyze.IsVendor(f.Name())) ||
|
||||
enry.IsDotFile(f.Name()) ||
|
||||
(!isDocumentation.Has() && enry.IsDocumentation(f.Name())) ||
|
||||
enry.IsConfiguration(f.Name()) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -170,7 +168,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !notGenerated && enry.IsGenerated(f.Name(), content) {
|
||||
if !isGenerated.Has() && enry.IsGenerated(f.Name(), content) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -193,13 +191,12 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
|
||||
included = langType == enry.Programming || langType == enry.Markup
|
||||
includedLanguage[language] = included
|
||||
}
|
||||
if included {
|
||||
if included || isDetectable.ValueOrDefault(false) {
|
||||
sizes[language] += f.Size()
|
||||
} else if len(sizes) == 0 && (firstExcludedLanguage == "" || firstExcludedLanguage == language) {
|
||||
firstExcludedLanguage = language
|
||||
firstExcludedLanguageSize += f.Size()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// If there are no included languages add the first excluded language
|
||||
|
@ -218,7 +218,7 @@ func searchIssueIsPull(t *testing.T) {
|
||||
SearchOptions{
|
||||
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 {
|
||||
@ -239,7 +239,7 @@ func searchIssueIsClosed(t *testing.T) {
|
||||
SearchOptions{
|
||||
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{
|
||||
@ -305,7 +305,7 @@ func searchIssueByLabelID(t *testing.T) {
|
||||
SearchOptions{
|
||||
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 {
|
||||
@ -329,7 +329,7 @@ func searchIssueByTime(t *testing.T) {
|
||||
SearchOptions{
|
||||
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 {
|
||||
@ -350,7 +350,7 @@ func searchIssueWithOrder(t *testing.T) {
|
||||
SearchOptions{
|
||||
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 {
|
||||
@ -410,8 +410,8 @@ func searchIssueWithPaginator(t *testing.T) {
|
||||
PageSize: 5,
|
||||
},
|
||||
},
|
||||
[]int64{17, 16, 15, 14, 13},
|
||||
20,
|
||||
[]int64{22, 21, 17, 16, 15},
|
||||
22,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
@ -1,48 +1,49 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package optional
|
||||
package optional_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOption(t *testing.T) {
|
||||
var uninitialized Option[int]
|
||||
var uninitialized optional.Option[int]
|
||||
assert.False(t, uninitialized.Has())
|
||||
assert.Equal(t, int(0), uninitialized.Value())
|
||||
assert.Equal(t, int(1), uninitialized.ValueOrDefault(1))
|
||||
|
||||
none := None[int]()
|
||||
none := optional.None[int]()
|
||||
assert.False(t, none.Has())
|
||||
assert.Equal(t, int(0), none.Value())
|
||||
assert.Equal(t, int(1), none.ValueOrDefault(1))
|
||||
|
||||
some := Some[int](1)
|
||||
some := optional.Some[int](1)
|
||||
assert.True(t, some.Has())
|
||||
assert.Equal(t, int(1), some.Value())
|
||||
assert.Equal(t, int(1), some.ValueOrDefault(2))
|
||||
|
||||
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.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.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.Equal(t, int(1), opt3.Value())
|
||||
}
|
||||
|
46
modules/optional/serialization.go
Normal 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
modules/optional/serialization_test.go
Normal 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")
|
||||
})
|
||||
}
|
||||
}
|
@ -3,14 +3,22 @@
|
||||
|
||||
package setting
|
||||
|
||||
import "code.gitea.io/gitea/modules/container"
|
||||
|
||||
// Admin settings
|
||||
var Admin struct {
|
||||
DisableRegularOrgCreation bool
|
||||
DefaultEmailNotification string
|
||||
UserDisabledFeatures container.Set[string]
|
||||
}
|
||||
|
||||
func loadAdminFrom(rootCfg ConfigProvider) {
|
||||
mustMapSetting(rootCfg, "admin", &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.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
|
||||
}
|
||||
|
||||
const (
|
||||
UserFeatureDeletion = "deletion"
|
||||
)
|
||||
|
@ -15,8 +15,45 @@ type PictureStruct struct {
|
||||
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 {
|
||||
Picture *PictureStruct
|
||||
Repository *RepositoryStruct
|
||||
}
|
||||
|
||||
var (
|
||||
@ -28,8 +65,11 @@ func initDefaultConfig() {
|
||||
config.SetCfgSecKeyGetter(&cfgSecKeyGetter{})
|
||||
defaultConfig = &ConfigStruct{
|
||||
Picture: &PictureStruct{
|
||||
DisableGravatar: config.Bool(false, config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}, "picture.disable_gravatar"),
|
||||
EnableFederatedAvatar: config.Bool(false, config.CfgSecKey{Sec: "picture", Key: "ENABLE_FEDERATED_AVATAR"}, "picture.enable_federated_avatar"),
|
||||
DisableGravatar: config.ValueJSON[bool]("picture.disable_gravatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}),
|
||||
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{}
|
||||
|
||||
func (c cfgSecKeyGetter) GetValue(sec, key string) (v string, has bool) {
|
||||
if key == "" {
|
||||
return "", false
|
||||
}
|
||||
cfgSec, err := CfgProvider.GetSection(sec)
|
||||
if err != nil {
|
||||
log.Error("Unable to get config section: %q", sec)
|
||||
|
@ -5,8 +5,11 @@ package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
type CfgSecKey struct {
|
||||
@ -23,14 +26,14 @@ type Value[T any] struct {
|
||||
revision int
|
||||
}
|
||||
|
||||
func (value *Value[T]) parse(s string) (v T) {
|
||||
switch any(v).(type) {
|
||||
case bool:
|
||||
b, _ := strconv.ParseBool(s)
|
||||
return any(b).(T)
|
||||
default:
|
||||
panic("unsupported config type, please complete the code")
|
||||
func (value *Value[T]) parse(key, valStr string) (v T) {
|
||||
v = value.def
|
||||
if valStr != "" {
|
||||
if err := json.Unmarshal(util.UnsafeStringToBytes(valStr), &v); err != nil {
|
||||
log.Error("Unable to unmarshal json config for key %q, err: %v", key, err)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
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 {
|
||||
v = value.def
|
||||
} else {
|
||||
v = value.parse(*valStr)
|
||||
v = value.parse(value.dynKey, *valStr)
|
||||
}
|
||||
|
||||
value.mu.Lock()
|
||||
@ -76,6 +79,16 @@ func (value *Value[T]) DynKey() string {
|
||||
return value.dynKey
|
||||
}
|
||||
|
||||
func Bool(def bool, cfgSecKey CfgSecKey, dynKey string) *Value[bool] {
|
||||
return &Value[bool]{def: def, cfgSecKey: cfgSecKey, dynKey: dynKey}
|
||||
func (value *Value[T]) WithDefault(def T) *Value[T] {
|
||||
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}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ var (
|
||||
ConnMaxLifetime time.Duration
|
||||
IterateBufferSize int
|
||||
AutoMigration bool
|
||||
SlowQueryThreshold time.Duration
|
||||
}{
|
||||
Timeout: 500,
|
||||
IterateBufferSize: 50,
|
||||
@ -87,6 +88,7 @@ func loadDBSetting(rootCfg ConfigProvider) {
|
||||
Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10)
|
||||
Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second)
|
||||
Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true)
|
||||
Database.SlowQueryThreshold = sec.Key("SLOW_QUERY_THRESHOLD").MustDuration(5 * time.Second)
|
||||
}
|
||||
|
||||
// DBConnStr returns database connection string
|
||||
|
@ -11,6 +11,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
@ -42,6 +44,22 @@ func (o OptionalBool) IsNone() bool {
|
||||
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
|
||||
func OptionalBoolOf(b bool) OptionalBool {
|
||||
if b {
|
||||
|
@ -249,6 +249,7 @@ email_title = Email Settings
|
||||
smtp_addr = SMTP Host
|
||||
smtp_port = SMTP Port
|
||||
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.
|
||||
mailer_user = SMTP Username
|
||||
mailer_password = SMTP Password
|
||||
@ -960,7 +961,7 @@ fork_branch = Branch to be cloned to the fork
|
||||
all_branches = All branches
|
||||
fork_no_valid_owners = This repository can not be forked because there are no valid owners.
|
||||
use_template = Use this template
|
||||
clone_in_vsc = Clone in VS Code
|
||||
open_with_editor = Open with %s
|
||||
download_zip = Download ZIP
|
||||
download_tar = Download TAR.GZ
|
||||
download_bundle = Download BUNDLE
|
||||
@ -1919,7 +1920,9 @@ wiki.original_git_entry_tooltip = View original Git file instead of using friend
|
||||
|
||||
activity = Activity
|
||||
activity.navbar.pulse = Pulse
|
||||
activity.navbar.code_frequency = Code Frequency
|
||||
activity.navbar.contributors = Contributors
|
||||
activity.navbar.recent_commits = Recent Commits
|
||||
activity.period.filter_label = Period:
|
||||
activity.period.daily = 1 day
|
||||
activity.period.halfweekly = 3 days
|
||||
@ -2598,7 +2601,9 @@ component_loading = Loading %s...
|
||||
component_loading_failed = Could not load %s
|
||||
component_loading_info = This might take a bit…
|
||||
component_failed_to_load = An unexpected error happened.
|
||||
code_frequency.what = code frequency
|
||||
contributors.what = contributions
|
||||
recent_commits.what = recent commits
|
||||
|
||||
[org]
|
||||
org_name_holder = Organization Name
|
||||
@ -2737,6 +2742,8 @@ integrations = Integrations
|
||||
authentication = Authentication Sources
|
||||
emails = User Emails
|
||||
config = Configuration
|
||||
config_summary = Summary
|
||||
config_settings = Settings
|
||||
notices = System Notices
|
||||
monitor = Monitoring
|
||||
first_page = First
|
||||
@ -3176,6 +3183,7 @@ config.picture_config = Picture and Avatar Configuration
|
||||
config.picture_service = Picture Service
|
||||
config.disable_gravatar = Disable Gravatar
|
||||
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_disable_diff_highlight = Disable Diff Syntax Highlight
|
||||
|
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640" xml:space="preserve" width="32" height="32"><path style="fill:#fff" d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z"/><path style="fill:#609926" d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z"/><path style="fill:#609926" d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8-1.9 8 2 16.3 9.1 20 7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3 7.8 4 17.4 1.7 22.5-5.3 5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8l-24.6 50.4z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.1 KiB |
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640" xml:space="preserve" width="32" height="32"><path style="fill:#fff" d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z"/><path style="fill:#609926" d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z"/><path style="fill:#609926" d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8-1.9 8 2 16.3 9.1 20 7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3 7.8 4 17.4 1.7 22.5-5.3 5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8l-24.6 50.4z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.1 KiB |
1
public/assets/img/svg/gitea-open-with-jetbrains.svg
generated
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 70 70" class="svg gitea-open-with-jetbrains" width="16" height="16" aria-hidden="true"><linearGradient id="gitea-open-with-jetbrains__a" x1=".79" x2="33.317" y1="40.089" y2="40.089" gradientUnits="userSpaceOnUse"><stop offset=".258" style="stop-color:#f97a12"/><stop offset=".459" style="stop-color:#b07b58"/><stop offset=".724" style="stop-color:#577bae"/><stop offset=".91" style="stop-color:#1e7ce5"/><stop offset="1" style="stop-color:#087cfa"/></linearGradient><path d="M17.7 54.6.8 41.2l8.4-15.6L33.3 35z" style="fill:url(#gitea-open-with-jetbrains__a)"/><linearGradient id="gitea-open-with-jetbrains__b" x1="25.767" x2="79.424" y1="24.88" y2="54.57" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#f97a12"/><stop offset=".072" style="stop-color:#cb7a3e"/><stop offset=".154" style="stop-color:#9e7b6a"/><stop offset=".242" style="stop-color:#757b91"/><stop offset=".334" style="stop-color:#537bb1"/><stop offset=".432" style="stop-color:#387ccc"/><stop offset=".538" style="stop-color:#237ce0"/><stop offset=".655" style="stop-color:#147cef"/><stop offset=".792" style="stop-color:#0b7cf7"/><stop offset="1" style="stop-color:#087cfa"/></linearGradient><path d="m70 18.7-1.3 40.5L41.8 70 25.6 59.6 49.3 35 38.9 12.3l9.3-11.2z" style="fill:url(#gitea-open-with-jetbrains__b)"/><linearGradient id="gitea-open-with-jetbrains__c" x1="63.228" x2="48.29" y1="42.915" y2="-1.719" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#fe315d"/><stop offset=".078" style="stop-color:#cb417e"/><stop offset=".16" style="stop-color:#9e4e9b"/><stop offset=".247" style="stop-color:#755bb4"/><stop offset=".339" style="stop-color:#5365ca"/><stop offset=".436" style="stop-color:#386ddb"/><stop offset=".541" style="stop-color:#2374e9"/><stop offset=".658" style="stop-color:#1478f3"/><stop offset=".794" style="stop-color:#0b7bf8"/><stop offset="1" style="stop-color:#087cfa"/></linearGradient><path d="M70 18.7 48.7 43.9l-9.8-31.6 9.3-11.2z" style="fill:url(#gitea-open-with-jetbrains__c)"/><linearGradient id="gitea-open-with-jetbrains__d" x1="10.72" x2="55.524" y1="16.473" y2="90.58" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#fe315d"/><stop offset=".04" style="stop-color:#f63462"/><stop offset=".104" style="stop-color:#df3a71"/><stop offset=".167" style="stop-color:#c24383"/><stop offset=".291" style="stop-color:#ad4a91"/><stop offset=".55" style="stop-color:#755bb4"/><stop offset=".917" style="stop-color:#1d76ed"/><stop offset="1" style="stop-color:#087cfa"/></linearGradient><path d="M33.7 58.1 5.6 68.3l4.5-15.8L16 33.1 0 27.7 10.1 0l22 2.7 21.6 24.7z" style="fill:url(#gitea-open-with-jetbrains__d)"/><path d="M13.7 13.5h43.2v43.2H13.7z" style="fill:#000"/><path d="M17.7 48.6h16.2v2.7H17.7zM29.4 22.4v-3.3h-9v3.3H23v11.3h-2.6V37h9v-3.3h-2.5V22.4zM38 37.3c-1.4 0-2.6-.3-3.5-.8s-1.7-1.2-2.3-1.9l2.5-2.8c.5.6 1 1 1.5 1.3s1.1.5 1.7.5c.7 0 1.3-.2 1.8-.7.4-.5.6-1.2.6-2.3V19.1h4v11.7c0 1.1-.1 2-.4 2.8s-.7 1.4-1.3 2c-.5.5-1.2 1-2 1.2-.8.3-1.6.5-2.6.5" style="fill:#fff"/></svg>
|
After Width: | Height: | Size: 3.0 KiB |
1
public/assets/img/svg/gitea-open-with-vscode.svg
generated
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 34 34" class="svg gitea-open-with-vscode" width="16" height="16" aria-hidden="true"><path d="M30.9 3.4 24.3.3a2 2 0 0 0-2.3.4L9.4 12.2 3.9 8c-.5-.4-1.2-.4-1.7 0L.4 9.8c-.5.5-.5 1.4 0 2L5.2 16 .4 20.3c-.5.6-.5 1.5 0 2L2.2 24c.5.5 1.2.5 1.7 0l5.5-4L22 31.2a2 2 0 0 0 2.3.4l6.6-3.2a2 2 0 0 0 1.1-1.8V5.2a2 2 0 0 0-1.1-1.8M24 23.3 14.4 16 24 8.7z"/></svg>
|
After Width: | Height: | Size: 406 B |
1
public/assets/img/svg/gitea-open-with-vscodium.svg
generated
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" clip-rule="evenodd" viewBox="0 0 16 16" class="svg gitea-open-with-vscodium" width="16" height="16" aria-hidden="true"><path fill-rule="nonzero" d="m10.2.2.5-.3c.3 0 .5.2.7.4l.2.8-.2 1-.8 2.4c-.3 1-.4 2 0 2.9l.8-2c.2 0 .4.1.4.3l-.3 1L9.2 13l3.1-2.9c.3-.2.7-.5.8-1a2 2 0 0 0-.3-1c-.2-.5-.5-.9-.6-1.4l.1-.7c.1-.1.3-.2.5-.1.2 0 .3.2.4.4.3.5.4 1.2.5 1.8l.6-1.2c0-.2.2-.4.4-.6l.4-.2c.2 0 .4.3.4.4v.6l-.8 1.6-1.4 1.8 1-.4c.2 0 .6.2.7.5 0 .2 0 .4-.2.5-.3.2-.6.2-1 .2-1 0-2.2.6-2.9 1.4L9.6 15c-.4.4-.9 1-1.4.8-.8-.1-.8-1.3-1-1.8 0-.3-.2-.6-.4-.7-.3-.2-.5-.3-.8-.3-.6-.1-1.2 0-1.8-.2l-.8-.4-.4-.7c-.3-.6-.3-1.2-.5-1.8A4 4 0 0 0 1 8l-.4-.4v-.4c.2-.2.5-.2.7 0 .5.2.5.8 1 1.1V6.2s.3-.1.4 0l.2.5L3 9c.4-.4.6-1 .5-1.5L3.4 7l.3-.2c.2 0 .3.2.4.3v.7c0 .6-.3 1.1-.4 1.7-.2.4-.3 1-.1 1.4.1.5.5.9.9 1 .5.3 1.1.4 1.7.4-.4-.6-.7-1.2-.7-2 0-.7.4-1.3.6-2C6.3 7 5.7 5.8 4.8 5l-1.5-.7c-.4-.2-.7-.7-.7-1.2.3-.1.7 0 1 .1L5 4.5l.6.1c.2-.3 0-.6-.2-.8-.3-.5-1-.6-1.3-1a.9.9 0 0 1-.2-.8c0-.2.3-.4.5-.4.4 0 .7.3.9.5.8.8 1.2 1.8 1.4 3s0 2.5-.2 3.7c0 .3-.2.5-.1.8l.2.2c.2 0 .4 0 .5-.2.4-.3.8-.8.9-1.3l.1-1.2.1-.6.4-.2.3.3v.6c-.1.5-.2 1-.5 1.6a2 2 0 0 1-.6 1l-1 1c-.1.2-.2.6-.1.9 0 .2.2.4.4.5.4.2.8.2 1 0 .3-.1.5-.4.7-.6l.5-1.4.4-2.5C9.7 7 9.6 6 9 5.2c-.2-.4-.5-.7-1-1l-1-.8c-.2-.3-.4-.7-.3-1.2h.6c.4.1.7.4.9.8s.4.8.9 1l-1-2c-.1-.3-.3-.5-.2-.8 0-.2.2-.4.4-.4s.4.1.5.3l.2.5 1 3.1a4 4 0 0 0 .4-2.3L10 1V.2Z"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
public/assets/img/svg/gitea-vscode.svg
generated
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 34 34" class="svg gitea-vscode" width="16" height="16" aria-hidden="true"><path d="M30.9 3.4 24.3.3a2 2 0 0 0-2.3.4L9.4 12.2 3.9 8c-.5-.4-1.2-.4-1.7 0L.4 9.8c-.5.5-.5 1.4 0 2L5.2 16 .4 20.3c-.5.6-.5 1.5 0 2L2.2 24c.5.5 1.2.5 1.7 0l5.5-4L22 31.2a2 2 0 0 0 2.3.4l6.6-3.2a2 2 0 0 0 1.1-1.8V5.2a2 2 0 0 0-1.1-1.8M24 23.3 14.4 16 24 8.7z"/></svg>
|
Before Width: | Height: | Size: 396 B |
@ -21,7 +21,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/user"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
@ -117,11 +116,8 @@ func CreateUser(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
}
|
||||
|
||||
if form.Restricted != nil {
|
||||
overwriteDefault.IsRestricted = util.OptionalBoolOf(*form.Restricted)
|
||||
IsActive: optional.Some(true),
|
||||
IsRestricted: optional.FromPtr(form.Restricted),
|
||||
}
|
||||
|
||||
if form.Visibility != "" {
|
||||
|
@ -1235,6 +1235,7 @@ func Routes() *web.Route {
|
||||
m.Group("/{ref}", func() {
|
||||
m.Get("/status", repo.GetCombinedCommitStatusByRef)
|
||||
m.Get("/statuses", repo.GetCommitStatusesByRef)
|
||||
m.Get("/pull", repo.GetCommitPullRequest)
|
||||
}, context.ReferencesGitRepo())
|
||||
}, reqRepoReader(unit.TypeCode))
|
||||
m.Group("/git", func() {
|
||||
|
@ -17,9 +17,9 @@ import (
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
@ -141,7 +141,7 @@ func DeleteBranch(ctx *context.APIContext) {
|
||||
// check whether branches of this repository has been synced
|
||||
totalNumOfBranches, err := db.Count[git_model.Branch](ctx, git_model.FindBranchOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
IsDeletedBranch: util.OptionalBoolFalse,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "CountBranches", err)
|
||||
@ -340,7 +340,7 @@ func ListBranches(ctx *context.APIContext) {
|
||||
branchOpts := git_model.FindBranchOptions{
|
||||
ListOptions: listOptions,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
IsDeletedBranch: util.OptionalBoolFalse,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
}
|
||||
var err error
|
||||
totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
@ -323,3 +324,53 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetCommitPullRequest returns the pull request of the commit
|
||||
func GetCommitPullRequest(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/commits/{sha}/pull repository repoGetCommitPullRequest
|
||||
// ---
|
||||
// summary: Get the pull request of the commit
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: sha
|
||||
// in: path
|
||||
// description: SHA of the commit to get
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/PullRequest"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
pr, err := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, ctx.Params(":sha"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrPullRequestNotExist(err) {
|
||||
ctx.Error(http.StatusNotFound, "GetPullRequestByMergedCommit", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err = pr.LoadBaseRepo(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
|
||||
return
|
||||
}
|
||||
if err = pr.LoadHeadRepo(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ func searchRefCommitByType(ctx *context.APIContext, refType, filter string) (str
|
||||
|
||||
// ConvertToObjectID returns a full-length SHA1 from a potential ID string
|
||||
func ConvertToObjectID(ctx gocontext.Context, repo *context.Repository, commitID string) (git.ObjectID, error) {
|
||||
objectFormat, _ := repo.GitRepo.GetObjectFormat()
|
||||
objectFormat := repo.GetObjectFormat()
|
||||
if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) {
|
||||
sha, err := git.NewIDFromString(commitID)
|
||||
if err == nil {
|
||||
|
@ -7,6 +7,7 @@ package install
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@ -25,12 +26,12 @@ import (
|
||||
"code.gitea.io/gitea/modules/generate"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/user"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
@ -419,6 +420,11 @@ func SubmitInstall(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(form.SMTPAddr)) > 0 {
|
||||
if _, err := mail.ParseAddress(form.SMTPFrom); err != nil {
|
||||
ctx.RenderWithErr(ctx.Tr("install.smtp_from_invalid"), tplInstall, &form)
|
||||
return
|
||||
}
|
||||
|
||||
cfg.Section("mailer").Key("ENABLED").SetValue("true")
|
||||
cfg.Section("mailer").Key("SMTP_ADDR").SetValue(form.SMTPAddr)
|
||||
cfg.Section("mailer").Key("SMTP_PORT").SetValue(form.SMTPPort)
|
||||
@ -533,8 +539,8 @@ func SubmitInstall(ctx *context.Context) {
|
||||
IsAdmin: true,
|
||||
}
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsRestricted: util.OptionalBoolFalse,
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsRestricted: optional.Some(false),
|
||||
IsActive: optional.Some(true),
|
||||
}
|
||||
|
||||
if err = user_model.CreateUser(ctx, u, overwriteDefault); err != nil {
|
||||
|
@ -145,7 +145,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
gitRepo := ctx.Repo.GitRepo
|
||||
objectFormat, _ := gitRepo.GetObjectFormat()
|
||||
objectFormat := ctx.Repo.GetObjectFormat()
|
||||
|
||||
if branchName == repo.DefaultBranch && newCommitID == objectFormat.EmptyObjectID().String() {
|
||||
log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo)
|
||||
|
@ -7,11 +7,11 @@ package admin
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
@ -24,7 +24,10 @@ import (
|
||||
"gitea.com/go-chi/session"
|
||||
)
|
||||
|
||||
const tplConfig base.TplName = "admin/config"
|
||||
const (
|
||||
tplConfig base.TplName = "admin/config"
|
||||
tplConfigSettings base.TplName = "admin/config_settings"
|
||||
)
|
||||
|
||||
// SendTestMail send test mail to confirm mail service is OK
|
||||
func SendTestMail(ctx *context.Context) {
|
||||
@ -98,8 +101,9 @@ func shadowPassword(provider, cfgItem string) string {
|
||||
|
||||
// Config show admin config page
|
||||
func Config(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("admin.config")
|
||||
ctx.Data["Title"] = ctx.Tr("admin.config_summary")
|
||||
ctx.Data["PageIsAdminConfig"] = true
|
||||
ctx.Data["PageIsAdminConfigSummary"] = true
|
||||
|
||||
ctx.Data["CustomConf"] = setting.CustomConf
|
||||
ctx.Data["AppUrl"] = setting.AppURL
|
||||
@ -161,23 +165,70 @@ func Config(ctx *context.Context) {
|
||||
|
||||
ctx.Data["Loggers"] = log.GetManager().DumpLoggers()
|
||||
config.GetDynGetter().InvalidateCache()
|
||||
ctx.Data["SystemConfig"] = setting.Config()
|
||||
prepareDeprecatedWarningsAlert(ctx)
|
||||
|
||||
ctx.HTML(http.StatusOK, tplConfig)
|
||||
}
|
||||
|
||||
func ConfigSettings(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("admin.config_settings")
|
||||
ctx.Data["PageIsAdminConfig"] = true
|
||||
ctx.Data["PageIsAdminConfigSettings"] = true
|
||||
ctx.Data["DefaultOpenWithEditorAppsString"] = setting.DefaultOpenWithEditorApps().ToTextareaString()
|
||||
ctx.HTML(http.StatusOK, tplConfigSettings)
|
||||
}
|
||||
|
||||
func ChangeConfig(ctx *context.Context) {
|
||||
key := strings.TrimSpace(ctx.FormString("key"))
|
||||
value := ctx.FormString("value")
|
||||
cfg := setting.Config()
|
||||
allowedKeys := container.SetOf(cfg.Picture.DisableGravatar.DynKey(), cfg.Picture.EnableFederatedAvatar.DynKey())
|
||||
if !allowedKeys.Contains(key) {
|
||||
|
||||
marshalBool := func(v string) (string, error) {
|
||||
if b, _ := strconv.ParseBool(v); b {
|
||||
return "true", nil
|
||||
}
|
||||
return "false", nil
|
||||
}
|
||||
marshalOpenWithApps := func(value string) (string, error) {
|
||||
lines := strings.Split(value, "\n")
|
||||
var openWithEditorApps setting.OpenWithEditorAppsType
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
displayName, openURL, ok := strings.Cut(line, "=")
|
||||
displayName, openURL = strings.TrimSpace(displayName), strings.TrimSpace(openURL)
|
||||
if !ok || displayName == "" || openURL == "" {
|
||||
continue
|
||||
}
|
||||
openWithEditorApps = append(openWithEditorApps, setting.OpenWithEditorApp{
|
||||
DisplayName: strings.TrimSpace(displayName),
|
||||
OpenURL: strings.TrimSpace(openURL),
|
||||
})
|
||||
}
|
||||
b, err := json.Marshal(openWithEditorApps)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
marshallers := map[string]func(string) (string, error){
|
||||
cfg.Picture.DisableGravatar.DynKey(): marshalBool,
|
||||
cfg.Picture.EnableFederatedAvatar.DynKey(): marshalBool,
|
||||
cfg.Repository.OpenWithEditorApps.DynKey(): marshalOpenWithApps,
|
||||
}
|
||||
marshaller, hasMarshaller := marshallers[key]
|
||||
if !hasMarshaller {
|
||||
ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key))
|
||||
return
|
||||
}
|
||||
if err := system_model.SetSettings(ctx, map[string]string{key: value}); err != nil {
|
||||
log.Error("set setting failed: %v", err)
|
||||
marshaledValue, err := marshaller(value)
|
||||
if err != nil {
|
||||
ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key))
|
||||
return
|
||||
}
|
||||
if err = system_model.SetSettings(ctx, map[string]string{key: marshaledValue}); err != nil {
|
||||
ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key))
|
||||
return
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ func NewUserPost(ctx *context.Context) {
|
||||
}
|
||||
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsActive: optional.Some(true),
|
||||
Visibility: &form.Visibility,
|
||||
}
|
||||
|
||||
|
@ -979,7 +979,7 @@ func SignInOAuthCallback(ctx *context.Context) {
|
||||
}
|
||||
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolOf(!setting.OAuth2Client.RegisterEmailConfirm && !setting.Service.RegisterManualConfirm),
|
||||
IsActive: optional.Some(!setting.OAuth2Client.RegisterEmailConfirm && !setting.Service.RegisterManualConfirm),
|
||||
}
|
||||
|
||||
source := authSource.Cfg.(*oauth2.Source)
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
@ -46,17 +45,6 @@ func Home(ctx *context.Context) {
|
||||
|
||||
ctx.Data["PageIsUserProfile"] = true
|
||||
ctx.Data["Title"] = org.DisplayName()
|
||||
if len(org.Description) != 0 {
|
||||
desc, err := markdown.RenderString(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
}, org.Description)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["RenderedDescription"] = desc
|
||||
}
|
||||
|
||||
var orderBy db.SearchOrderBy
|
||||
ctx.Data["SortType"] = ctx.FormString("sort")
|
||||
@ -131,18 +119,12 @@ func Home(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var isFollowing bool
|
||||
if ctx.Doer != nil {
|
||||
isFollowing = user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
|
||||
}
|
||||
|
||||
ctx.Data["Repos"] = repos
|
||||
ctx.Data["Total"] = count
|
||||
ctx.Data["Members"] = members
|
||||
ctx.Data["Teams"] = ctx.Org.Teams
|
||||
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
|
||||
ctx.Data["PageIsViewRepositories"] = true
|
||||
ctx.Data["IsFollowing"] = isFollowing
|
||||
|
||||
err = shared_user.LoadHeaderCount(ctx)
|
||||
if err != nil {
|
||||
|
@ -132,11 +132,8 @@ type blameResult struct {
|
||||
}
|
||||
|
||||
func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, file string, bypassBlameIgnore bool) (*blameResult, error) {
|
||||
objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
|
||||
if err != nil {
|
||||
ctx.NotFound("CreateBlameReader", err)
|
||||
return nil, err
|
||||
}
|
||||
objectFormat := ctx.Repo.GetObjectFormat()
|
||||
|
||||
blameReader, err := git.CreateBlameReader(ctx, objectFormat, repoPath, commit, file, bypassBlameIgnore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
41
routers/web/repo/code_frequency.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
contributors_service "code.gitea.io/gitea/services/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
tplCodeFrequency base.TplName = "repo/activity"
|
||||
)
|
||||
|
||||
// CodeFrequency renders the page to show repository code frequency
|
||||
func CodeFrequency(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.activity.navbar.code_frequency")
|
||||
|
||||
ctx.Data["PageIsActivity"] = true
|
||||
ctx.Data["PageIsCodeFrequency"] = true
|
||||
ctx.PageData["repoLink"] = ctx.Repo.RepoLink
|
||||
|
||||
ctx.HTML(http.StatusOK, tplCodeFrequency)
|
||||
}
|
||||
|
||||
// CodeFrequencyData returns JSON of code frequency data
|
||||
func CodeFrequencyData(ctx *context.Context) {
|
||||
if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil {
|
||||
if errors.Is(err, contributors_service.ErrAwaitGeneration) {
|
||||
ctx.Status(http.StatusAccepted)
|
||||
return
|
||||
}
|
||||
ctx.ServerError("GetCodeFrequencyData", err)
|
||||
} else {
|
||||
ctx.JSON(http.StatusOK, contributorStats["total"].Weeks)
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
@ -311,14 +312,14 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
|
||||
baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(ci.BaseBranch)
|
||||
baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(ci.BaseBranch)
|
||||
baseIsTag := ctx.Repo.GitRepo.IsTagExist(ci.BaseBranch)
|
||||
objectFormat, _ := ctx.Repo.GitRepo.GetObjectFormat()
|
||||
|
||||
if !baseIsCommit && !baseIsBranch && !baseIsTag {
|
||||
// Check if baseBranch is short sha commit hash
|
||||
if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(ci.BaseBranch); baseCommit != nil {
|
||||
ci.BaseBranch = baseCommit.ID.String()
|
||||
ctx.Data["BaseBranch"] = ci.BaseBranch
|
||||
baseIsCommit = true
|
||||
} else if ci.BaseBranch == objectFormat.EmptyObjectID().String() {
|
||||
} else if ci.BaseBranch == ctx.Repo.GetObjectFormat().EmptyObjectID().String() {
|
||||
if isSameRepo {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadBranch))
|
||||
} else {
|
||||
@ -700,7 +701,7 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
IsDeletedBranch: util.OptionalBoolFalse,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -757,7 +758,7 @@ func CompareDiff(ctx *context.Context) {
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
IsDeletedBranch: util.OptionalBoolFalse,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranches", err)
|
||||
|
@ -161,9 +161,6 @@ func editFile(ctx *context.Context, isNewFile bool) {
|
||||
}
|
||||
|
||||
d, _ := io.ReadAll(dataRc)
|
||||
if err := dataRc.Close(); err != nil {
|
||||
log.Error("Error whilst closing blob data: %v", err)
|
||||
}
|
||||
|
||||
buf = append(buf, d...)
|
||||
if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil {
|
||||
|
@ -711,16 +711,12 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is
|
||||
tmp.ItemID = -review.ReviewerTeamID
|
||||
}
|
||||
|
||||
if ctx.Repo.IsAdmin() {
|
||||
// Admin can dismiss or re-request any review requests
|
||||
if canChooseReviewer {
|
||||
// Users who can choose reviewers can also remove review requests
|
||||
tmp.CanChange = true
|
||||
} else if ctx.Doer != nil && ctx.Doer.ID == review.ReviewerID && review.Type == issues_model.ReviewTypeRequest {
|
||||
// A user can refuse review requests
|
||||
tmp.CanChange = true
|
||||
} else if (canChooseReviewer || (ctx.Doer != nil && ctx.Doer.ID == issue.PosterID)) && review.Type != issues_model.ReviewTypeRequest &&
|
||||
ctx.Doer.ID != review.ReviewerID {
|
||||
// The poster of the PR, a manager, or official reviewers can re-request review from other reviewers
|
||||
tmp.CanChange = true
|
||||
}
|
||||
|
||||
pullReviews = append(pullReviews, tmp)
|
||||
@ -1525,18 +1521,9 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if issue.IsPull {
|
||||
canChooseReviewer := ctx.Repo.CanWrite(unit.TypePullRequests)
|
||||
canChooseReviewer := false
|
||||
if ctx.Doer != nil && ctx.IsSigned {
|
||||
if !canChooseReviewer {
|
||||
canChooseReviewer = ctx.Doer.ID == issue.PosterID
|
||||
}
|
||||
if !canChooseReviewer {
|
||||
canChooseReviewer, err = issues_model.IsOfficialReviewer(ctx, issue, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("IsOfficialReviewer", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
canChooseReviewer = issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, repo, issue)
|
||||
}
|
||||
|
||||
RetrieveRepoReviewers(ctx, repo, issue, canChooseReviewer)
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
issue_template "code.gitea.io/gitea/modules/issue/template"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/upload"
|
||||
@ -186,7 +187,7 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository {
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
IsDeletedBranch: util.OptionalBoolFalse,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
// Add it as the first option
|
||||
ExcludeBranchNames: []string{ctx.Repo.Repository.DefaultBranch},
|
||||
})
|
||||
|
41
routers/web/repo/recent_commits.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
contributors_service "code.gitea.io/gitea/services/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
tplRecentCommits base.TplName = "repo/activity"
|
||||
)
|
||||
|
||||
// RecentCommits renders the page to show recent commit frequency on repository
|
||||
func RecentCommits(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.activity.navbar.recent_commits")
|
||||
|
||||
ctx.Data["PageIsActivity"] = true
|
||||
ctx.Data["PageIsRecentCommits"] = true
|
||||
ctx.PageData["repoLink"] = ctx.Repo.RepoLink
|
||||
|
||||
ctx.HTML(http.StatusOK, tplRecentCommits)
|
||||
}
|
||||
|
||||
// RecentCommitsData returns JSON of recent commits data
|
||||
func RecentCommitsData(ctx *context.Context) {
|
||||
if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil {
|
||||
if errors.Is(err, contributors_service.ErrAwaitGeneration) {
|
||||
ctx.Status(http.StatusAccepted)
|
||||
return
|
||||
}
|
||||
ctx.ServerError("RecentCommitsData", err)
|
||||
} else {
|
||||
ctx.JSON(http.StatusOK, contributorStats["total"].Weeks)
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
@ -685,7 +686,7 @@ type branchTagSearchResponse struct {
|
||||
func GetBranchesList(ctx *context.Context) {
|
||||
branchOpts := git_model.FindBranchOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
IsDeletedBranch: util.OptionalBoolFalse,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
@ -720,7 +721,7 @@ func GetTagList(ctx *context.Context) {
|
||||
func PrepareBranchList(ctx *context.Context) {
|
||||
branchOpts := git_model.FindBranchOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
IsDeletedBranch: util.OptionalBoolFalse,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
|
@ -388,7 +388,7 @@ func LFSFileFind(ctx *context.Context) {
|
||||
sha := ctx.FormString("sha")
|
||||
ctx.Data["Title"] = oid
|
||||
ctx.Data["PageIsSettingsLFS"] = true
|
||||
objectFormat, _ := ctx.Repo.GitRepo.GetObjectFormat()
|
||||
objectFormat := ctx.Repo.GetObjectFormat()
|
||||
var objectID git.ObjectID
|
||||
if len(sha) == 0 {
|
||||
pointer := lfs.Pointer{Oid: oid, Size: size}
|
||||
|
@ -45,6 +45,7 @@ import (
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/web/feed"
|
||||
@ -634,11 +635,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
||||
defer deferable()
|
||||
attrs, err := checker.CheckPath(ctx.Repo.TreePath)
|
||||
if err == nil {
|
||||
vendored, has := attrs["linguist-vendored"]
|
||||
ctx.Data["IsVendored"] = has && (vendored == "set" || vendored == "true")
|
||||
|
||||
generated, has := attrs["linguist-generated"]
|
||||
ctx.Data["IsGenerated"] = has && (generated == "set" || generated == "true")
|
||||
ctx.Data["IsVendored"] = git.AttributeToBool(attrs, git.AttributeLinguistVendored).Value()
|
||||
ctx.Data["IsGenerated"] = git.AttributeToBool(attrs, git.AttributeLinguistGenerated).Value()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -792,7 +790,7 @@ func Home(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
renderCode(ctx)
|
||||
renderHomeCode(ctx)
|
||||
}
|
||||
|
||||
// LastCommit returns lastCommit data for the provided branch/tag/commit and directory (in url) and filenames in body
|
||||
@ -919,9 +917,33 @@ func renderRepoTopics(ctx *context.Context) {
|
||||
ctx.Data["Topics"] = topics
|
||||
}
|
||||
|
||||
func renderCode(ctx *context.Context) {
|
||||
func prepareOpenWithEditorApps(ctx *context.Context) {
|
||||
var tmplApps []map[string]any
|
||||
apps := setting.Config().Repository.OpenWithEditorApps.Value(ctx)
|
||||
if len(apps) == 0 {
|
||||
apps = setting.DefaultOpenWithEditorApps()
|
||||
}
|
||||
for _, app := range apps {
|
||||
schema, _, _ := strings.Cut(app.OpenURL, ":")
|
||||
var iconHTML template.HTML
|
||||
if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
|
||||
iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-open-with-%s", schema), 16, "gt-mr-3")
|
||||
} else {
|
||||
iconHTML = svg.RenderHTML("gitea-git", 16, "gt-mr-3") // TODO: it could support user's customized icon in the future
|
||||
}
|
||||
tmplApps = append(tmplApps, map[string]any{
|
||||
"DisplayName": app.DisplayName,
|
||||
"OpenURL": app.OpenURL,
|
||||
"IconHTML": iconHTML,
|
||||
})
|
||||
}
|
||||
ctx.Data["OpenWithEditorApps"] = tmplApps
|
||||
}
|
||||
|
||||
func renderHomeCode(ctx *context.Context) {
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled
|
||||
prepareOpenWithEditorApps(ctx)
|
||||
|
||||
if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() {
|
||||
showEmpty := true
|
||||
|
@ -17,8 +17,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
@ -36,7 +34,6 @@ func prepareContextForCommonProfile(ctx *context.Context) {
|
||||
func PrepareContextForProfileBigAvatar(ctx *context.Context) {
|
||||
prepareContextForCommonProfile(ctx)
|
||||
|
||||
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
|
||||
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate
|
||||
ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location)
|
||||
|
||||
@ -48,18 +45,6 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
|
||||
}
|
||||
ctx.Data["OpenIDs"] = openIDs
|
||||
|
||||
if len(ctx.ContextUser.Description) != 0 {
|
||||
content, err := markdown.RenderString(&markup.RenderContext{
|
||||
Metas: map[string]string{"mode": "document"},
|
||||
Ctx: ctx,
|
||||
}, ctx.ContextUser.Description)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderString", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["RenderedDescription"] = content
|
||||
}
|
||||
|
||||
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
|
||||
orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{
|
||||
UserID: ctx.ContextUser.ID,
|
||||
|
@ -324,6 +324,7 @@ func Action(ctx *context.Context) {
|
||||
ctx.HTML(http.StatusOK, tplProfileBigAvatar)
|
||||
return
|
||||
} else if ctx.ContextUser.IsOrganization() {
|
||||
ctx.Data["Org"] = ctx.ContextUser
|
||||
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
|
||||
ctx.HTML(http.StatusOK, tplFollowUnfollow)
|
||||
return
|
||||
|
@ -233,6 +233,11 @@ func DeleteEmail(ctx *context.Context) {
|
||||
|
||||
// DeleteAccount render user suicide page and response for delete user himself
|
||||
func DeleteAccount(ctx *context.Context) {
|
||||
if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureDeletion) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsAccount"] = true
|
||||
|
||||
@ -299,6 +304,7 @@ func loadAccountData(ctx *context.Context) {
|
||||
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
|
||||
ctx.Data["ActivationsPending"] = pendingActivation
|
||||
ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
|
||||
ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures
|
||||
|
||||
if setting.Service.UserDeleteWithCommentsMaxTime != 0 {
|
||||
ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String()
|
||||
|
@ -686,6 +686,7 @@ func registerRoutes(m *web.Route) {
|
||||
m.Get("", admin.Config)
|
||||
m.Post("", admin.ChangeConfig)
|
||||
m.Post("/test_mail", admin.SendTestMail)
|
||||
m.Get("/settings", admin.ConfigSettings)
|
||||
})
|
||||
|
||||
m.Group("/monitor", func() {
|
||||
@ -1398,6 +1399,14 @@ func registerRoutes(m *web.Route) {
|
||||
m.Get("", repo.Contributors)
|
||||
m.Get("/data", repo.ContributorsData)
|
||||
})
|
||||
m.Group("/code-frequency", func() {
|
||||
m.Get("", repo.CodeFrequency)
|
||||
m.Get("/data", repo.CodeFrequencyData)
|
||||
})
|
||||
m.Group("/recent-commits", func() {
|
||||
m.Get("", repo.RecentCommits)
|
||||
m.Get("/data", repo.RecentCommitsData)
|
||||
})
|
||||
}, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases))
|
||||
|
||||
m.Group("/activity_author_data", func() {
|
||||
|
@ -36,7 +36,7 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
|
||||
|
||||
topicBranch = opts.GitPushOptions["topic"]
|
||||
_, forcePush = opts.GitPushOptions["force-push"]
|
||||
objectFormat, _ := gitRepo.GetObjectFormat()
|
||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
|
||||
|
||||
pusher, err := user_model.GetUserByID(ctx, opts.UserID)
|
||||
if err != nil {
|
||||
@ -149,7 +149,6 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
|
||||
|
||||
log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
|
||||
|
||||
objectFormat, _ := gitRepo.GetObjectFormat()
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
Ref: pr.GetGitRefName(),
|
||||
OriginalRef: opts.RefFullNames[i],
|
||||
|
@ -40,6 +40,7 @@ func isContainerPath(req *http.Request) bool {
|
||||
var (
|
||||
gitRawOrAttachPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(?:(?:git-(?:(?:upload)|(?:receive))-pack$)|(?:info/refs$)|(?:HEAD$)|(?:objects/)|(?:raw/)|(?:releases/download/)|(?:attachments/))`)
|
||||
lfsPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/info/lfs/`)
|
||||
archivePathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/archive/`)
|
||||
)
|
||||
|
||||
func isGitRawOrAttachPath(req *http.Request) bool {
|
||||
@ -56,6 +57,10 @@ func isGitRawOrAttachOrLFSPath(req *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isArchivePath(req *http.Request) bool {
|
||||
return archivePathRe.MatchString(req.URL.Path)
|
||||
}
|
||||
|
||||
// handleSignIn clears existing session variables and stores new ones for the specified user object
|
||||
func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *user_model.User) {
|
||||
// We need to regenerate the session...
|
||||
|
@ -133,7 +133,7 @@ func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store Dat
|
||||
func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
||||
// These paths are not API paths, but we still want to check for tokens because they maybe in the API returned URLs
|
||||
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) &&
|
||||
!isGitRawOrAttachPath(req) {
|
||||
!isGitRawOrAttachPath(req) && !isArchivePath(req) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,8 @@ import (
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
|
||||
gouuid "github.com/google/uuid"
|
||||
@ -161,7 +161,7 @@ func (r *ReverseProxy) newUser(req *http.Request) *user_model.User {
|
||||
}
|
||||
|
||||
overwriteDefault := user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsActive: optional.Some(true),
|
||||
}
|
||||
|
||||
if err := user_model.CreateUser(req.Context(), user, &overwriteDefault); err != nil {
|
||||
|
@ -13,7 +13,6 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
auth_module "code.gitea.io/gitea/modules/auth"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
source_service "code.gitea.io/gitea/services/auth/source"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
)
|
||||
@ -85,8 +84,8 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
||||
IsAdmin: sr.IsAdmin,
|
||||
}
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsRestricted: util.OptionalBoolOf(sr.IsRestricted),
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsRestricted: optional.Some(sr.IsRestricted),
|
||||
IsActive: optional.Some(true),
|
||||
}
|
||||
|
||||
err := user_model.CreateUser(ctx, user, overwriteDefault)
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
source_service "code.gitea.io/gitea/services/auth/source"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
)
|
||||
@ -125,8 +124,8 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
|
||||
IsAdmin: su.IsAdmin,
|
||||
}
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsRestricted: util.OptionalBoolOf(su.IsRestricted),
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsRestricted: optional.Some(su.IsRestricted),
|
||||
IsActive: optional.Some(true),
|
||||
}
|
||||
|
||||
err = user_model.CreateUser(ctx, usr, overwriteDefault)
|
||||
|
@ -11,8 +11,8 @@ import (
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/auth/pam"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@ -60,7 +60,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
||||
LoginName: userName, // This is what the user typed in
|
||||
}
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsActive: optional.Some(true),
|
||||
}
|
||||
|
||||
if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil {
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
@ -75,7 +76,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
||||
LoginName: userName,
|
||||
}
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsActive: optional.Some(true),
|
||||
}
|
||||
|
||||
if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil {
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
gitea_context "code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
@ -172,8 +173,8 @@ func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) (
|
||||
}
|
||||
emailNotificationPreference := user_model.EmailNotificationsDisabled
|
||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||
IsActive: util.OptionalBoolOf(cfg.AutoActivateUsers),
|
||||
KeepEmailPrivate: util.OptionalBoolTrue,
|
||||
IsActive: optional.Some(cfg.AutoActivateUsers),
|
||||
KeepEmailPrivate: optional.Some(true),
|
||||
EmailNotificationsPreference: &emailNotificationPreference,
|
||||
}
|
||||
if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil {
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/highlight"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
|
||||
@ -1181,41 +1182,30 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
|
||||
|
||||
for _, diffFile := range diff.Files {
|
||||
|
||||
gotVendor := false
|
||||
gotGenerated := false
|
||||
isVendored := optional.None[bool]()
|
||||
isGenerated := optional.None[bool]()
|
||||
if checker != nil {
|
||||
attrs, err := checker.CheckPath(diffFile.Name)
|
||||
if err == nil {
|
||||
if vendored, has := attrs["linguist-vendored"]; has {
|
||||
if vendored == "set" || vendored == "true" {
|
||||
diffFile.IsVendored = true
|
||||
gotVendor = true
|
||||
} else {
|
||||
gotVendor = vendored == "false"
|
||||
}
|
||||
}
|
||||
if generated, has := attrs["linguist-generated"]; has {
|
||||
if generated == "set" || generated == "true" {
|
||||
diffFile.IsGenerated = true
|
||||
gotGenerated = true
|
||||
} else {
|
||||
gotGenerated = generated == "false"
|
||||
}
|
||||
}
|
||||
if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" {
|
||||
diffFile.Language = language
|
||||
} else if language, has := attrs["gitlab-language"]; has && language != "unspecified" && language != "" {
|
||||
diffFile.Language = language
|
||||
isVendored = git.AttributeToBool(attrs, git.AttributeLinguistVendored)
|
||||
isGenerated = git.AttributeToBool(attrs, git.AttributeLinguistGenerated)
|
||||
|
||||
language := git.TryReadLanguageAttribute(attrs)
|
||||
if language.Has() {
|
||||
diffFile.Language = language.Value()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !gotVendor {
|
||||
diffFile.IsVendored = analyze.IsVendor(diffFile.Name)
|
||||
if !isVendored.Has() {
|
||||
isVendored = optional.Some(analyze.IsVendor(diffFile.Name))
|
||||
}
|
||||
if !gotGenerated {
|
||||
diffFile.IsGenerated = analyze.IsGenerated(diffFile.Name)
|
||||
diffFile.IsVendored = isVendored.Value()
|
||||
|
||||
if !isGenerated.Has() {
|
||||
isGenerated = optional.Some(analyze.IsGenerated(diffFile.Name))
|
||||
}
|
||||
diffFile.IsGenerated = isGenerated.Value()
|
||||
|
||||
tailSection := diffFile.GetTailSection(gitRepo, opts.BeforeCommitID, opts.AfterCommitID)
|
||||
if tailSection != nil {
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@ -113,10 +114,10 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User,
|
||||
return err
|
||||
}
|
||||
|
||||
var pemResult bool
|
||||
canDoerChangeReviewRequests := CanDoerChangeReviewRequests(ctx, doer, issue.Repo, issue)
|
||||
|
||||
if isAdd {
|
||||
pemResult = permReviewer.CanAccessAny(perm.AccessModeRead, unit.TypePullRequests)
|
||||
if !pemResult {
|
||||
if !permReviewer.CanAccessAny(perm.AccessModeRead, unit.TypePullRequests) {
|
||||
return issues_model.ErrNotValidReviewRequest{
|
||||
Reason: "Reviewer can't read",
|
||||
UserID: doer.ID,
|
||||
@ -124,28 +125,6 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User,
|
||||
}
|
||||
}
|
||||
|
||||
if doer.ID == issue.PosterID && issue.OriginalAuthorID == 0 && lastreview != nil && lastreview.Type != issues_model.ReviewTypeRequest {
|
||||
return nil
|
||||
}
|
||||
|
||||
pemResult = doer.ID == issue.PosterID
|
||||
if !pemResult {
|
||||
pemResult = permDoer.CanAccessAny(perm.AccessModeWrite, unit.TypePullRequests)
|
||||
}
|
||||
if !pemResult {
|
||||
pemResult, err = issues_model.IsOfficialReviewer(ctx, issue, doer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !pemResult {
|
||||
return issues_model.ErrNotValidReviewRequest{
|
||||
Reason: "Doer can't choose reviewer",
|
||||
UserID: doer.ID,
|
||||
RepoID: issue.Repo.ID,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if reviewer.ID == issue.PosterID && issue.OriginalAuthorID == 0 {
|
||||
return issues_model.ErrNotValidReviewRequest{
|
||||
Reason: "poster of pr can't be reviewer",
|
||||
@ -153,22 +132,35 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User,
|
||||
RepoID: issue.Repo.ID,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if lastreview != nil && lastreview.Type == issues_model.ReviewTypeRequest && lastreview.ReviewerID == doer.ID {
|
||||
|
||||
if canDoerChangeReviewRequests {
|
||||
return nil
|
||||
}
|
||||
|
||||
if doer.ID == issue.PosterID && issue.OriginalAuthorID == 0 && lastreview != nil && lastreview.Type != issues_model.ReviewTypeRequest {
|
||||
return nil
|
||||
}
|
||||
|
||||
pemResult = permDoer.IsAdmin()
|
||||
if !pemResult {
|
||||
return issues_model.ErrNotValidReviewRequest{
|
||||
Reason: "Doer is not admin",
|
||||
Reason: "Doer can't choose reviewer",
|
||||
UserID: doer.ID,
|
||||
RepoID: issue.Repo.ID,
|
||||
}
|
||||
}
|
||||
|
||||
if canDoerChangeReviewRequests {
|
||||
return nil
|
||||
}
|
||||
|
||||
if lastreview != nil && lastreview.Type == issues_model.ReviewTypeRequest && lastreview.ReviewerID == doer.ID {
|
||||
return nil
|
||||
}
|
||||
|
||||
return issues_model.ErrNotValidReviewRequest{
|
||||
Reason: "Doer can't remove reviewer",
|
||||
UserID: doer.ID,
|
||||
RepoID: issue.Repo.ID,
|
||||
}
|
||||
}
|
||||
|
||||
// IsValidTeamReviewRequest Check permission for ReviewRequest Team
|
||||
@ -181,11 +173,7 @@ func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team,
|
||||
}
|
||||
}
|
||||
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
|
||||
if err != nil {
|
||||
log.Error("Unable to GetUserRepoPermission for %-v in %-v#%d", doer, issue.Repo, issue.Index)
|
||||
return err
|
||||
}
|
||||
canDoerChangeReviewRequests := CanDoerChangeReviewRequests(ctx, doer, issue.Repo, issue)
|
||||
|
||||
if isAdd {
|
||||
if issue.Repo.IsPrivate {
|
||||
@ -200,30 +188,26 @@ func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team,
|
||||
}
|
||||
}
|
||||
|
||||
doerCanWrite := permission.CanAccessAny(perm.AccessModeWrite, unit.TypePullRequests)
|
||||
if !doerCanWrite && doer.ID != issue.PosterID {
|
||||
official, err := issues_model.IsOfficialReviewer(ctx, issue, doer)
|
||||
if err != nil {
|
||||
log.Error("Unable to Check if IsOfficialReviewer for %-v in %-v#%d", doer, issue.Repo, issue.Index)
|
||||
return err
|
||||
if canDoerChangeReviewRequests {
|
||||
return nil
|
||||
}
|
||||
if !official {
|
||||
|
||||
return issues_model.ErrNotValidReviewRequest{
|
||||
Reason: "Doer can't choose reviewer",
|
||||
UserID: doer.ID,
|
||||
RepoID: issue.Repo.ID,
|
||||
}
|
||||
}
|
||||
|
||||
if canDoerChangeReviewRequests {
|
||||
return nil
|
||||
}
|
||||
} else if !permission.IsAdmin() {
|
||||
|
||||
return issues_model.ErrNotValidReviewRequest{
|
||||
Reason: "Only admin users can remove team requests. Doer is not admin",
|
||||
Reason: "Doer can't remove reviewer",
|
||||
UserID: doer.ID,
|
||||
RepoID: issue.Repo.ID,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it.
|
||||
@ -264,3 +248,50 @@ func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *use
|
||||
|
||||
return comment, err
|
||||
}
|
||||
|
||||
// CanDoerChangeReviewRequests returns if the doer can add/remove review requests of a PR
|
||||
func CanDoerChangeReviewRequests(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue) bool {
|
||||
// The poster of the PR can change the reviewers
|
||||
if doer.ID == issue.PosterID {
|
||||
return true
|
||||
}
|
||||
|
||||
// The owner of the repo can change the reviewers
|
||||
if doer.ID == repo.OwnerID {
|
||||
return true
|
||||
}
|
||||
|
||||
// Collaborators of the repo can change the reviewers
|
||||
isCollaborator, err := repo_model.IsCollaborator(ctx, repo.ID, doer.ID)
|
||||
if err != nil {
|
||||
log.Error("IsCollaborator: %v", err)
|
||||
return false
|
||||
}
|
||||
if isCollaborator {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the repo's owner is an organization, members of teams with read permission on pull requests can change reviewers
|
||||
if repo.Owner.IsOrganization() {
|
||||
teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead)
|
||||
if err != nil {
|
||||
log.Error("GetTeamsWithAccessToRepo: %v", err)
|
||||
return false
|
||||
}
|
||||
for _, team := range teams {
|
||||
if !team.UnitEnabled(ctx, unit.TypePullRequests) {
|
||||
continue
|
||||
}
|
||||
isMember, err := organization.IsTeamMember(ctx, repo.OwnerID, team.ID, doer.ID)
|
||||
if err != nil {
|
||||
log.Error("IsTeamMember: %v", err)
|
||||
continue
|
||||
}
|
||||
if isMember {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -140,8 +140,18 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.gitRepo, err = gitrepo.OpenRepository(g.ctx, r)
|
||||
g.gitRepo, err = gitrepo.OpenRepository(g.ctx, g.repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// detect object format from git repository and update to database
|
||||
objectFormat, err := g.gitRepo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.repo.ObjectFormatName = objectFormat.Name()
|
||||
return repo_model.UpdateRepositoryCols(g.ctx, g.repo, "object_format_name")
|
||||
}
|
||||
|
||||
// Close closes this uploader
|
||||
@ -482,10 +492,16 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
|
||||
}
|
||||
case issues_model.CommentTypeChangeTitle:
|
||||
if comment.Meta["OldTitle"] != nil {
|
||||
cm.OldTitle = fmt.Sprintf("%s", comment.Meta["OldTitle"])
|
||||
cm.OldTitle = fmt.Sprint(comment.Meta["OldTitle"])
|
||||
}
|
||||
if comment.Meta["NewTitle"] != nil {
|
||||
cm.NewTitle = fmt.Sprintf("%s", comment.Meta["NewTitle"])
|
||||
cm.NewTitle = fmt.Sprint(comment.Meta["NewTitle"])
|
||||
}
|
||||
case issues_model.CommentTypeChangeTargetBranch:
|
||||
if comment.Meta["OldRef"] != nil && comment.Meta["NewRef"] != nil {
|
||||
cm.OldRef = fmt.Sprint(comment.Meta["OldRef"])
|
||||
cm.NewRef = fmt.Sprint(comment.Meta["NewRef"])
|
||||
cm.Content = ""
|
||||
}
|
||||
case issues_model.CommentTypePRScheduledToAutoMerge, issues_model.CommentTypePRUnScheduledToAutoMerge:
|
||||
cm.Content = ""
|
||||
@ -896,7 +912,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
|
||||
comment.UpdatedAt = comment.CreatedAt
|
||||
}
|
||||
|
||||
objectFormat, _ := g.gitRepo.GetObjectFormat()
|
||||
objectFormat := git.ObjectFormatFromName(g.repo.ObjectFormatName)
|
||||
if !objectFormat.IsValid(comment.CommitID) {
|
||||
log.Warn("Invalid comment CommitID[%s] on comment[%d] in PR #%d of %s/%s replaced with %s", comment.CommitID, pr.Index, g.repoOwner, g.repoName, headCommitID)
|
||||
comment.CommitID = headCommitID
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -519,6 +520,8 @@ func (g *GitlabDownloader) GetComments(commentable base.Commentable) ([]*base.Co
|
||||
return allComments, true, nil
|
||||
}
|
||||
|
||||
var targetBranchChangeRegexp = regexp.MustCompile("^changed target branch from `(.*?)` to `(.*?)`$")
|
||||
|
||||
func (g *GitlabDownloader) convertNoteToComment(localIndex int64, note *gitlab.Note) *base.Comment {
|
||||
comment := &base.Comment{
|
||||
IssueIndex: localIndex,
|
||||
@ -528,11 +531,16 @@ func (g *GitlabDownloader) convertNoteToComment(localIndex int64, note *gitlab.N
|
||||
PosterEmail: note.Author.Email,
|
||||
Content: note.Body,
|
||||
Created: *note.CreatedAt,
|
||||
Meta: map[string]any{},
|
||||
}
|
||||
|
||||
// Try to find the underlying event of system notes.
|
||||
if note.System {
|
||||
if strings.HasPrefix(note.Body, "enabled an automatic merge") {
|
||||
if match := targetBranchChangeRegexp.FindStringSubmatch(note.Body); match != nil {
|
||||
comment.CommentType = issues_model.CommentTypeChangeTargetBranch.String()
|
||||
comment.Meta["OldRef"] = match[1]
|
||||
comment.Meta["NewRef"] = match[2]
|
||||
} else if strings.HasPrefix(note.Body, "enabled an automatic merge") {
|
||||
comment.CommentType = issues_model.CommentTypePRScheduledToAutoMerge.String()
|
||||
} else if note.Body == "canceled the automatic merge" {
|
||||
comment.CommentType = issues_model.CommentTypePRUnScheduledToAutoMerge.String()
|
||||
|
@ -545,7 +545,8 @@ func TestNoteToComment(t *testing.T) {
|
||||
notes := []gitlab.Note{
|
||||
makeTestNote(1, "This is a regular comment", false),
|
||||
makeTestNote(2, "enabled an automatic merge for abcd1234", true),
|
||||
makeTestNote(3, "canceled the automatic merge", true),
|
||||
makeTestNote(3, "changed target branch from `master` to `main`", true),
|
||||
makeTestNote(4, "canceled the automatic merge", true),
|
||||
}
|
||||
comments := []base.Comment{{
|
||||
IssueIndex: 17,
|
||||
@ -556,6 +557,7 @@ func TestNoteToComment(t *testing.T) {
|
||||
CommentType: "",
|
||||
Content: "This is a regular comment",
|
||||
Created: now,
|
||||
Meta: map[string]any{},
|
||||
}, {
|
||||
IssueIndex: 17,
|
||||
Index: 2,
|
||||
@ -565,15 +567,30 @@ func TestNoteToComment(t *testing.T) {
|
||||
CommentType: "pull_scheduled_merge",
|
||||
Content: "enabled an automatic merge for abcd1234",
|
||||
Created: now,
|
||||
Meta: map[string]any{},
|
||||
}, {
|
||||
IssueIndex: 17,
|
||||
Index: 3,
|
||||
PosterID: 72,
|
||||
PosterName: "test",
|
||||
PosterEmail: "test@example.com",
|
||||
CommentType: "change_target_branch",
|
||||
Content: "changed target branch from `master` to `main`",
|
||||
Created: now,
|
||||
Meta: map[string]any{
|
||||
"OldRef": "master",
|
||||
"NewRef": "main",
|
||||
},
|
||||
}, {
|
||||
IssueIndex: 17,
|
||||
Index: 4,
|
||||
PosterID: 72,
|
||||
PosterName: "test",
|
||||
PosterEmail: "test@example.com",
|
||||
CommentType: "pull_cancel_scheduled_merge",
|
||||
Content: "canceled the automatic merge",
|
||||
Created: now,
|
||||
Meta: map[string]any{},
|
||||
}}
|
||||
|
||||
for i, note := range notes {
|
||||
|
@ -222,10 +222,7 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
objectFormat, err := gitRepo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%-v GetObjectFormat: %w", pr.BaseRepo, err)
|
||||
}
|
||||
objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName)
|
||||
|
||||
// Get the commit from BaseBranch where the pull request got merged
|
||||
mergeCommit, _, err := git.NewCommand(ctx, "rev-list", "--ancestry-path", "--merges", "--reverse").
|
||||
|