mirror of
https://github.com/go-gitea/gitea
synced 2025-01-05 11:45:55 +01:00
Merge branch 'main' into lunny/automerge_support_delete_branch
This commit is contained in:
commit
7cddaeb9c4
@ -642,7 +642,7 @@ rules:
|
|||||||
no-this-before-super: [2]
|
no-this-before-super: [2]
|
||||||
no-throw-literal: [2]
|
no-throw-literal: [2]
|
||||||
no-undef-init: [2]
|
no-undef-init: [2]
|
||||||
no-undef: [2, {typeof: true}]
|
no-undef: [2, {typeof: true}] # TODO: disable this rule after tsc passes
|
||||||
no-undefined: [0]
|
no-undefined: [0]
|
||||||
no-underscore-dangle: [0]
|
no-underscore-dangle: [0]
|
||||||
no-unexpected-multiline: [2]
|
no-unexpected-multiline: [2]
|
||||||
|
8
.github/workflows/pull-compliance.yml
vendored
8
.github/workflows/pull-compliance.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
|||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: pip install poetry
|
- run: pip install poetry
|
||||||
@ -66,7 +66,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
@ -137,7 +137,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
@ -186,7 +186,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend
|
- run: make deps-frontend
|
||||||
|
10
.github/workflows/pull-db-tests.yml
vendored
10
.github/workflows/pull-db-tests.yml
vendored
@ -154,12 +154,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:8.0
|
# the bitnami mysql image has more options than the official one, it's easier to customize
|
||||||
|
image: bitnami/mysql:8.0
|
||||||
env:
|
env:
|
||||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
ALLOW_EMPTY_PASSWORD: true
|
||||||
MYSQL_DATABASE: testgitea
|
MYSQL_DATABASE: testgitea
|
||||||
ports:
|
ports:
|
||||||
- "3306:3306"
|
- "3306:3306"
|
||||||
|
options: >-
|
||||||
|
--mount type=tmpfs,destination=/bitnami/mysql/data
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
image: elasticsearch:7.5.0
|
image: elasticsearch:7.5.0
|
||||||
env:
|
env:
|
||||||
@ -188,7 +191,8 @@ jobs:
|
|||||||
- name: run migration tests
|
- name: run migration tests
|
||||||
run: make test-mysql-migration
|
run: make test-mysql-migration
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: make integration-test-coverage
|
# run: make integration-test-coverage (at the moment, no coverage is really handled)
|
||||||
|
run: make test-mysql
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
RACE_ENABLED: true
|
RACE_ENABLED: true
|
||||||
|
2
.github/workflows/pull-e2e-tests.yml
vendored
2
.github/workflows/pull-e2e-tests.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend frontend deps-backend
|
- run: make deps-frontend frontend deps-backend
|
||||||
|
2
.github/workflows/release-nightly.yml
vendored
2
.github/workflows/release-nightly.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
|
2
.github/workflows/release-tag-rc.yml
vendored
2
.github/workflows/release-tag-rc.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
|
2
.github/workflows/release-tag-version.yml
vendored
2
.github/workflows/release-tag-version.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: package-lock.json
|
cache-dependency-path: package-lock.json
|
||||||
- run: make deps-frontend deps-backend
|
- run: make deps-frontend deps-backend
|
||||||
|
@ -111,13 +111,11 @@ func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error
|
|||||||
if !setting.IsProd {
|
if !setting.IsProd {
|
||||||
_, _ = fmt.Fprintln(os.Stderr, "Gitea:", logMsg)
|
_, _ = fmt.Fprintln(os.Stderr, "Gitea:", logMsg)
|
||||||
}
|
}
|
||||||
if userMessage != "" {
|
|
||||||
if unicode.IsPunct(rune(userMessage[len(userMessage)-1])) {
|
if unicode.IsPunct(rune(userMessage[len(userMessage)-1])) {
|
||||||
logMsg = userMessage + " " + logMsg
|
logMsg = userMessage + " " + logMsg
|
||||||
} else {
|
} else {
|
||||||
logMsg = userMessage + ". " + logMsg
|
logMsg = userMessage + ". " + logMsg
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ = private.SSHLog(ctx, true, logMsg)
|
_ = private.SSHLog(ctx, true, logMsg)
|
||||||
}
|
}
|
||||||
return cli.Exit("", 1)
|
return cli.Exit("", 1)
|
||||||
@ -288,10 +286,10 @@ func runServ(c *cli.Context) error {
|
|||||||
if allowedCommands.Contains(verb) {
|
if allowedCommands.Contains(verb) {
|
||||||
if allowedCommandsLfs.Contains(verb) {
|
if allowedCommandsLfs.Contains(verb) {
|
||||||
if !setting.LFS.StartServer {
|
if !setting.LFS.StartServer {
|
||||||
return fail(ctx, "Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
|
return fail(ctx, "LFS Server is not enabled", "")
|
||||||
}
|
}
|
||||||
if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
|
if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
|
||||||
return fail(ctx, "Unknown git command", "LFS SSH transfer connection denied, pure SSH protocol is disabled")
|
return fail(ctx, "LFS SSH transfer is not enabled", "")
|
||||||
}
|
}
|
||||||
if len(words) > 2 {
|
if len(words) > 2 {
|
||||||
lfsVerb = words[2]
|
lfsVerb = words[2]
|
||||||
|
@ -1007,6 +1007,14 @@ LEVEL = Info
|
|||||||
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
|
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
|
||||||
;DEFAULT_FORK_REPO_UNITS = repo.code,repo.pulls
|
;DEFAULT_FORK_REPO_UNITS = repo.code,repo.pulls
|
||||||
;;
|
;;
|
||||||
|
;; Comma separated list of default mirror repo units.
|
||||||
|
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
|
||||||
|
;DEFAULT_MIRROR_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.wiki,repo.projects,repo.packages
|
||||||
|
;;
|
||||||
|
;; Comma separated list of default template repo units.
|
||||||
|
;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS.
|
||||||
|
;DEFAULT_TEMPLATE_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages
|
||||||
|
;;
|
||||||
;; Prefix archive files by placing them in a directory named after the repository
|
;; Prefix archive files by placing them in a directory named after the repository
|
||||||
;PREFIX_ARCHIVE_FILES = true
|
;PREFIX_ARCHIVE_FILES = true
|
||||||
;;
|
;;
|
||||||
@ -1904,7 +1912,7 @@ LEVEL = Info
|
|||||||
;ENABLED = true
|
;ENABLED = true
|
||||||
;;
|
;;
|
||||||
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||||
;ALLOWED_TYPES = .csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip
|
;ALLOWED_TYPES = .avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip
|
||||||
;;
|
;;
|
||||||
;; Max size of each file. Defaults to 2048MB
|
;; Max size of each file. Defaults to 2048MB
|
||||||
;MAX_SIZE = 2048
|
;MAX_SIZE = 2048
|
||||||
|
12
flake.lock
12
flake.lock
@ -5,11 +5,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710146030,
|
"lastModified": 1726560853,
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1720542800,
|
"lastModified": 1731139594,
|
||||||
"narHash": "sha256-ZgnNHuKV6h2+fQ5LuqnUaqZey1Lqqt5dTUAiAnqH0QQ=",
|
"narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "feb2849fdeb70028c70d73b848214b00d324a497",
|
"rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -22,14 +22,13 @@
|
|||||||
gzip
|
gzip
|
||||||
|
|
||||||
# frontend
|
# frontend
|
||||||
nodejs_20
|
nodejs_22
|
||||||
|
|
||||||
# linting
|
# linting
|
||||||
python312
|
python312
|
||||||
poetry
|
poetry
|
||||||
|
|
||||||
# backend
|
# backend
|
||||||
go_1_22
|
|
||||||
gofumpt
|
gofumpt
|
||||||
sqlite
|
sqlite
|
||||||
];
|
];
|
||||||
|
5
go.mod
5
go.mod
@ -88,7 +88,7 @@ require (
|
|||||||
github.com/markbates/goth v1.80.0
|
github.com/markbates/goth v1.80.0
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
github.com/meilisearch/meilisearch-go v0.29.0
|
github.com/meilisearch/meilisearch-go v0.29.1-0.20241106140435-0bf60fad690a
|
||||||
github.com/mholt/archiver/v3 v3.5.1
|
github.com/mholt/archiver/v3 v3.5.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27
|
github.com/microcosm-cc/bluemonday v1.0.27
|
||||||
github.com/microsoft/go-mssqldb v1.7.2
|
github.com/microsoft/go-mssqldb v1.7.2
|
||||||
@ -222,7 +222,7 @@ require (
|
|||||||
github.com/go-openapi/validate v0.24.0 // indirect
|
github.com/go-openapi/validate v0.24.0 // indirect
|
||||||
github.com/go-webauthn/x v0.1.15 // indirect
|
github.com/go-webauthn/x v0.1.15 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
||||||
@ -330,6 +330,7 @@ replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142
|
|||||||
|
|
||||||
replace github.com/nektos/act => gitea.com/gitea/act v0.261.3
|
replace github.com/nektos/act => gitea.com/gitea/act v0.261.3
|
||||||
|
|
||||||
|
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
|
||||||
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
|
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
|
||||||
|
|
||||||
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
|
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
|
||||||
|
9
go.sum
9
go.sum
@ -98,7 +98,6 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1L
|
|||||||
github.com/anchore/archiver/v3 v3.5.2 h1:Bjemm2NzuRhmHy3m0lRe5tNoClB9A4zYyDV58PaB6aA=
|
github.com/anchore/archiver/v3 v3.5.2 h1:Bjemm2NzuRhmHy3m0lRe5tNoClB9A4zYyDV58PaB6aA=
|
||||||
github.com/anchore/archiver/v3 v3.5.2/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
github.com/anchore/archiver/v3 v3.5.2/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||||
@ -391,8 +390,8 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7w
|
|||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
|
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
|
||||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
|
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
@ -603,8 +602,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
|
|||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/meilisearch/meilisearch-go v0.29.0 h1:HZ9NEKN59USINQ/DXJge/aaXq8IrsKbXGTdAoBaaDz4=
|
github.com/meilisearch/meilisearch-go v0.29.1-0.20241106140435-0bf60fad690a h1:F0y+3QtCG00mr4KueQWuHv1tlIQeNXhH+XAKYLhb3X4=
|
||||||
github.com/meilisearch/meilisearch-go v0.29.0/go.mod h1:2cRCAn4ddySUsFfNDLVPod/plRibQsJkXF/4gLhxbOk=
|
github.com/meilisearch/meilisearch-go v0.29.1-0.20241106140435-0bf60fad690a/go.mod h1:NYOgjEGt/+oExD+NixreBMqxtIB0kCndXOOgpGhoqEs=
|
||||||
github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw=
|
github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw=
|
||||||
github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
|
github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||||
|
@ -261,6 +261,7 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InsertRun inserts a run
|
// InsertRun inserts a run
|
||||||
|
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||||
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
|
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -273,6 +274,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
run.Index = index
|
run.Index = index
|
||||||
|
run.Title, _ = util.SplitStringAtByteN(run.Title, 255)
|
||||||
|
|
||||||
if err := db.Insert(ctx, run); err != nil {
|
if err := db.Insert(ctx, run); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -399,6 +401,7 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
|
|||||||
if len(cols) > 0 {
|
if len(cols) > 0 {
|
||||||
sess.Cols(cols...)
|
sess.Cols(cols...)
|
||||||
}
|
}
|
||||||
|
run.Title, _ = util.SplitStringAtByteN(run.Title, 255)
|
||||||
affected, err := sess.Update(run)
|
affected, err := sess.Update(run)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -252,6 +252,7 @@ func GetRunnerByID(ctx context.Context, id int64) (*ActionRunner, error) {
|
|||||||
// UpdateRunner updates runner's information.
|
// UpdateRunner updates runner's information.
|
||||||
func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
|
func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
r.Name, _ = util.SplitStringAtByteN(r.Name, 255)
|
||||||
var err error
|
var err error
|
||||||
if len(cols) == 0 {
|
if len(cols) == 0 {
|
||||||
_, err = e.ID(r.ID).AllCols().Update(r)
|
_, err = e.ID(r.ID).AllCols().Update(r)
|
||||||
@ -278,6 +279,7 @@ func CreateRunner(ctx context.Context, t *ActionRunner) error {
|
|||||||
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
||||||
t.OwnerID = 0
|
t.OwnerID = 0
|
||||||
}
|
}
|
||||||
|
t.Name, _ = util.SplitStringAtByteN(t.Name, 255)
|
||||||
return db.Insert(ctx, t)
|
return db.Insert(ctx, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,6 +68,7 @@ func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error {
|
|||||||
|
|
||||||
// Loop through each schedule row
|
// Loop through each schedule row
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
|
row.Title, _ = util.SplitStringAtByteN(row.Title, 255)
|
||||||
// Create new schedule row
|
// Create new schedule row
|
||||||
if err = db.Insert(ctx, row); err != nil {
|
if err = db.Insert(ctx, row); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -341,7 +341,7 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
|
|||||||
// UpdateTaskByState updates the task by the state.
|
// UpdateTaskByState updates the task by the state.
|
||||||
// It will always update the task if the state is not final, even there is no change.
|
// It will always update the task if the state is not final, even there is no change.
|
||||||
// So it will update ActionTask.Updated to avoid the task being judged as a zombie task.
|
// So it will update ActionTask.Updated to avoid the task being judged as a zombie task.
|
||||||
func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionTask, error) {
|
func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.TaskState) (*ActionTask, error) {
|
||||||
stepStates := map[int64]*runnerv1.StepState{}
|
stepStates := map[int64]*runnerv1.StepState{}
|
||||||
for _, v := range state.Steps {
|
for _, v := range state.Steps {
|
||||||
stepStates[v.Id] = v
|
stepStates[v.Id] = v
|
||||||
@ -360,6 +360,8 @@ func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionT
|
|||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return nil, util.ErrNotExist
|
return nil, util.ErrNotExist
|
||||||
|
} else if runnerID != task.RunnerID {
|
||||||
|
return nil, fmt.Errorf("invalid runner for task")
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.Status.IsDone() {
|
if task.Status.IsDone() {
|
||||||
|
@ -251,6 +251,9 @@ func (a *Action) GetActDisplayNameTitle(ctx context.Context) string {
|
|||||||
// GetRepoUserName returns the name of the action repository owner.
|
// GetRepoUserName returns the name of the action repository owner.
|
||||||
func (a *Action) GetRepoUserName(ctx context.Context) string {
|
func (a *Action) GetRepoUserName(ctx context.Context) string {
|
||||||
a.loadRepo(ctx)
|
a.loadRepo(ctx)
|
||||||
|
if a.Repo == nil {
|
||||||
|
return "(non-existing-repo)"
|
||||||
|
}
|
||||||
return a.Repo.OwnerName
|
return a.Repo.OwnerName
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,6 +266,9 @@ func (a *Action) ShortRepoUserName(ctx context.Context) string {
|
|||||||
// GetRepoName returns the name of the action repository.
|
// GetRepoName returns the name of the action repository.
|
||||||
func (a *Action) GetRepoName(ctx context.Context) string {
|
func (a *Action) GetRepoName(ctx context.Context) string {
|
||||||
a.loadRepo(ctx)
|
a.loadRepo(ctx)
|
||||||
|
if a.Repo == nil {
|
||||||
|
return "(non-existing-repo)"
|
||||||
|
}
|
||||||
return a.Repo.Name
|
return a.Repo.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
"xorm.io/xorm/schemas"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -50,25 +51,64 @@ const (
|
|||||||
// Notification represents a notification
|
// Notification represents a notification
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
UserID int64 `xorm:"INDEX NOT NULL"`
|
UserID int64 `xorm:"NOT NULL"`
|
||||||
RepoID int64 `xorm:"INDEX NOT NULL"`
|
RepoID int64 `xorm:"NOT NULL"`
|
||||||
|
|
||||||
Status NotificationStatus `xorm:"SMALLINT INDEX NOT NULL"`
|
Status NotificationStatus `xorm:"SMALLINT NOT NULL"`
|
||||||
Source NotificationSource `xorm:"SMALLINT INDEX NOT NULL"`
|
Source NotificationSource `xorm:"SMALLINT NOT NULL"`
|
||||||
|
|
||||||
IssueID int64 `xorm:"INDEX NOT NULL"`
|
IssueID int64 `xorm:"NOT NULL"`
|
||||||
CommitID string `xorm:"INDEX"`
|
CommitID string
|
||||||
CommentID int64
|
CommentID int64
|
||||||
|
|
||||||
UpdatedBy int64 `xorm:"INDEX NOT NULL"`
|
UpdatedBy int64 `xorm:"NOT NULL"`
|
||||||
|
|
||||||
Issue *issues_model.Issue `xorm:"-"`
|
Issue *issues_model.Issue `xorm:"-"`
|
||||||
Repository *repo_model.Repository `xorm:"-"`
|
Repository *repo_model.Repository `xorm:"-"`
|
||||||
Comment *issues_model.Comment `xorm:"-"`
|
Comment *issues_model.Comment `xorm:"-"`
|
||||||
User *user_model.User `xorm:"-"`
|
User *user_model.User `xorm:"-"`
|
||||||
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
|
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"`
|
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableIndices implements xorm's TableIndices interface
|
||||||
|
func (n *Notification) TableIndices() []*schemas.Index {
|
||||||
|
indices := make([]*schemas.Index, 0, 8)
|
||||||
|
usuuIndex := schemas.NewIndex("u_s_uu", schemas.IndexType)
|
||||||
|
usuuIndex.AddColumn("user_id", "status", "updated_unix")
|
||||||
|
indices = append(indices, usuuIndex)
|
||||||
|
|
||||||
|
// Add the individual indices that were previously defined in struct tags
|
||||||
|
userIDIndex := schemas.NewIndex("idx_notification_user_id", schemas.IndexType)
|
||||||
|
userIDIndex.AddColumn("user_id")
|
||||||
|
indices = append(indices, userIDIndex)
|
||||||
|
|
||||||
|
repoIDIndex := schemas.NewIndex("idx_notification_repo_id", schemas.IndexType)
|
||||||
|
repoIDIndex.AddColumn("repo_id")
|
||||||
|
indices = append(indices, repoIDIndex)
|
||||||
|
|
||||||
|
statusIndex := schemas.NewIndex("idx_notification_status", schemas.IndexType)
|
||||||
|
statusIndex.AddColumn("status")
|
||||||
|
indices = append(indices, statusIndex)
|
||||||
|
|
||||||
|
sourceIndex := schemas.NewIndex("idx_notification_source", schemas.IndexType)
|
||||||
|
sourceIndex.AddColumn("source")
|
||||||
|
indices = append(indices, sourceIndex)
|
||||||
|
|
||||||
|
issueIDIndex := schemas.NewIndex("idx_notification_issue_id", schemas.IndexType)
|
||||||
|
issueIDIndex.AddColumn("issue_id")
|
||||||
|
indices = append(indices, issueIDIndex)
|
||||||
|
|
||||||
|
commitIDIndex := schemas.NewIndex("idx_notification_commit_id", schemas.IndexType)
|
||||||
|
commitIDIndex.AddColumn("commit_id")
|
||||||
|
indices = append(indices, commitIDIndex)
|
||||||
|
|
||||||
|
updatedByIndex := schemas.NewIndex("idx_notification_updated_by", schemas.IndexType)
|
||||||
|
updatedByIndex.AddColumn("updated_by")
|
||||||
|
indices = append(indices, updatedByIndex)
|
||||||
|
|
||||||
|
return indices
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -68,7 +68,8 @@ func CheckCollations(x *xorm.Engine) (*CheckCollationsResult, error) {
|
|||||||
|
|
||||||
var candidateCollations []string
|
var candidateCollations []string
|
||||||
if x.Dialect().URI().DBType == schemas.MYSQL {
|
if x.Dialect().URI().DBType == schemas.MYSQL {
|
||||||
if _, err = x.SQL("SELECT @@collation_database").Get(&res.DatabaseCollation); err != nil {
|
_, err = x.SQL("SELECT DEFAULT_COLLATION_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", setting.Database.Name).Get(&res.DatabaseCollation)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res.IsCollationCaseSensitive = func(s string) bool {
|
res.IsCollationCaseSensitive = func(s string) bool {
|
||||||
|
@ -1,3 +1,22 @@
|
|||||||
|
-
|
||||||
|
id: 46
|
||||||
|
attempt: 3
|
||||||
|
runner_id: 1
|
||||||
|
status: 3 # 3 is the status code for "cancelled"
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
repo_id: 4
|
||||||
|
owner_id: 1
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4260c64a69a2cc1508825121b7b8394e48e00b1bf8718b2aaaaa
|
||||||
|
token_salt: eeeeeeee
|
||||||
|
token_last_eight: eeeeeeee
|
||||||
|
log_filename: artifact-test2/2f/47.log
|
||||||
|
log_in_storage: 1
|
||||||
|
log_length: 707
|
||||||
|
log_size: 90179
|
||||||
|
log_expired: 0
|
||||||
-
|
-
|
||||||
id: 47
|
id: 47
|
||||||
job_id: 192
|
job_id: 192
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
id: 2
|
id: 2
|
||||||
oid: 2eccdb43825d2a49d99d542daa20075cff1d97d9d2349a8977efe9c03661737c
|
oid: 2eccdb43825d2a49d99d542daa20075cff1d97d9d2349a8977efe9c03661737c
|
||||||
size: 107
|
size: 2048
|
||||||
repository_id: 54
|
repository_id: 54
|
||||||
created_unix: 1671607299
|
created_unix: 1671607299
|
||||||
|
|
||||||
|
@ -129,3 +129,9 @@
|
|||||||
uid: 2
|
uid: 2
|
||||||
org_id: 35
|
org_id: 35
|
||||||
is_public: true
|
is_public: true
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 23
|
||||||
|
uid: 20
|
||||||
|
org_id: 17
|
||||||
|
is_public: false
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
fork_id: 0
|
fork_id: 0
|
||||||
is_template: false
|
is_template: false
|
||||||
template_id: 0
|
template_id: 0
|
||||||
size: 8478
|
size: 0
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
close_issues_via_commit_in_any_branch: false
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
-
|
-
|
||||||
id: 1
|
id: 1
|
||||||
setting_key: 'picture.disable_gravatar'
|
setting_key: 'picture.disable_gravatar'
|
||||||
setting_value: 'false'
|
setting_value: 'true'
|
||||||
version: 1
|
version: 1
|
||||||
created: 1653533198
|
created: 1653533198
|
||||||
updated: 1653533198
|
updated: 1653533198
|
||||||
|
@ -23,9 +23,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar1
|
avatar: ""
|
||||||
avatar_email: user1@example.com
|
avatar_email: user1@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -60,8 +60,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar2
|
avatar: ""
|
||||||
avatar_email: user2@example.com
|
avatar_email: user2@example.com
|
||||||
|
# cause a random avatar to be generated when referenced for test purposes
|
||||||
use_custom_avatar: false
|
use_custom_avatar: false
|
||||||
num_followers: 2
|
num_followers: 2
|
||||||
num_following: 1
|
num_following: 1
|
||||||
@ -97,9 +98,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar3
|
avatar: ""
|
||||||
avatar_email: org3@example.com
|
avatar_email: org3@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -134,9 +135,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar4
|
avatar: ""
|
||||||
avatar_email: user4@example.com
|
avatar_email: user4@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 1
|
num_following: 1
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -171,9 +172,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: false
|
allow_create_organization: false
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar5
|
avatar: ""
|
||||||
avatar_email: user5@example.com
|
avatar_email: user5@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -208,9 +209,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar6
|
avatar: ""
|
||||||
avatar_email: org6@example.com
|
avatar_email: org6@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -245,9 +246,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar7
|
avatar: ""
|
||||||
avatar_email: org7@example.com
|
avatar_email: org7@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -282,9 +283,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar8
|
avatar: ""
|
||||||
avatar_email: user8@example.com
|
avatar_email: user8@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 1
|
num_followers: 1
|
||||||
num_following: 1
|
num_following: 1
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -319,9 +320,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar9
|
avatar: ""
|
||||||
avatar_email: user9@example.com
|
avatar_email: user9@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -332,6 +333,7 @@
|
|||||||
repo_admin_change_team_access: false
|
repo_admin_change_team_access: false
|
||||||
theme: ""
|
theme: ""
|
||||||
keep_activity_private: false
|
keep_activity_private: false
|
||||||
|
created_unix: 1730468968
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 10
|
id: 10
|
||||||
@ -356,9 +358,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar10
|
avatar: ""
|
||||||
avatar_email: user10@example.com
|
avatar_email: user10@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 2
|
num_stars: 2
|
||||||
@ -393,9 +395,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar11
|
avatar: ""
|
||||||
avatar_email: user11@example.com
|
avatar_email: user11@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -430,9 +432,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar12
|
avatar: ""
|
||||||
avatar_email: user12@example.com
|
avatar_email: user12@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -467,9 +469,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar13
|
avatar: ""
|
||||||
avatar_email: user13@example.com
|
avatar_email: user13@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -504,9 +506,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar14
|
avatar: ""
|
||||||
avatar_email: user13@example.com
|
avatar_email: user13@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -541,9 +543,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar15
|
avatar: ""
|
||||||
avatar_email: user15@example.com
|
avatar_email: user15@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -578,9 +580,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar16
|
avatar: ""
|
||||||
avatar_email: user16@example.com
|
avatar_email: user16@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -615,15 +617,15 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar17
|
avatar: ""
|
||||||
avatar_email: org17@example.com
|
avatar_email: org17@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_repos: 2
|
num_repos: 2
|
||||||
num_teams: 3
|
num_teams: 3
|
||||||
num_members: 4
|
num_members: 5
|
||||||
visibility: 0
|
visibility: 0
|
||||||
repo_admin_change_team_access: false
|
repo_admin_change_team_access: false
|
||||||
theme: ""
|
theme: ""
|
||||||
@ -652,9 +654,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar18
|
avatar: ""
|
||||||
avatar_email: user18@example.com
|
avatar_email: user18@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -689,9 +691,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar19
|
avatar: ""
|
||||||
avatar_email: org19@example.com
|
avatar_email: org19@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -726,9 +728,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar20
|
avatar: ""
|
||||||
avatar_email: user20@example.com
|
avatar_email: user20@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -763,9 +765,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar21
|
avatar: ""
|
||||||
avatar_email: user21@example.com
|
avatar_email: user21@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -800,9 +802,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar22
|
avatar: ""
|
||||||
avatar_email: limited_org@example.com
|
avatar_email: limited_org@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -837,9 +839,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar23
|
avatar: ""
|
||||||
avatar_email: privated_org@example.com
|
avatar_email: privated_org@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -874,9 +876,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar24
|
avatar: ""
|
||||||
avatar_email: user24@example.com
|
avatar_email: user24@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -911,9 +913,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar25
|
avatar: ""
|
||||||
avatar_email: org25@example.com
|
avatar_email: org25@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -948,9 +950,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar26
|
avatar: ""
|
||||||
avatar_email: org26@example.com
|
avatar_email: org26@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -985,9 +987,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar27
|
avatar: ""
|
||||||
avatar_email: user27@example.com
|
avatar_email: user27@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1022,9 +1024,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar28
|
avatar: ""
|
||||||
avatar_email: user28@example.com
|
avatar_email: user28@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1059,9 +1061,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar29
|
avatar: ""
|
||||||
avatar_email: user29@example.com
|
avatar_email: user29@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1096,9 +1098,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar29
|
avatar: ""
|
||||||
avatar_email: user30@example.com
|
avatar_email: user30@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1133,9 +1135,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar31
|
avatar: ""
|
||||||
avatar_email: user31@example.com
|
avatar_email: user31@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 1
|
num_following: 1
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1170,9 +1172,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar32
|
avatar: ""
|
||||||
avatar_email: user30@example.com
|
avatar_email: user30@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1207,9 +1209,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar33
|
avatar: ""
|
||||||
avatar_email: user33@example.com
|
avatar_email: user33@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 1
|
num_followers: 1
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1245,7 +1247,7 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: false
|
allow_create_organization: false
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar34
|
avatar: ""
|
||||||
avatar_email: user34@example.com
|
avatar_email: user34@example.com
|
||||||
use_custom_avatar: true
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
@ -1282,9 +1284,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar35
|
avatar: ""
|
||||||
avatar_email: private_org35@example.com
|
avatar_email: private_org35@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1319,9 +1321,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar22
|
avatar: ""
|
||||||
avatar_email: abcde@gitea.com
|
avatar_email: abcde@gitea.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1356,9 +1358,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: true
|
prohibit_login: true
|
||||||
avatar: avatar29
|
avatar: ""
|
||||||
avatar_email: user37@example.com
|
avatar_email: user37@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1393,9 +1395,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar38
|
avatar: ""
|
||||||
avatar_email: user38@example.com
|
avatar_email: user38@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1430,9 +1432,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar39
|
avatar: ""
|
||||||
avatar_email: user39@example.com
|
avatar_email: user39@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1467,9 +1469,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar40
|
avatar: ""
|
||||||
avatar_email: user40@example.com
|
avatar_email: user40@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1504,9 +1506,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar41
|
avatar: ""
|
||||||
avatar_email: org41@example.com
|
avatar_email: org41@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
@ -1541,9 +1543,9 @@
|
|||||||
allow_import_local: false
|
allow_import_local: false
|
||||||
allow_create_organization: true
|
allow_create_organization: true
|
||||||
prohibit_login: false
|
prohibit_login: false
|
||||||
avatar: avatar42
|
avatar: ""
|
||||||
avatar_email: org42@example.com
|
avatar_email: org42@example.com
|
||||||
use_custom_avatar: false
|
use_custom_avatar: true
|
||||||
num_followers: 0
|
num_followers: 0
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
@ -138,6 +139,7 @@ func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User,
|
|||||||
}
|
}
|
||||||
defer committer.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
|
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
|
||||||
if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
|
if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
|
||||||
return fmt.Errorf("updateIssueCols: %w", err)
|
return fmt.Errorf("updateIssueCols: %w", err)
|
||||||
}
|
}
|
||||||
@ -386,6 +388,7 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewIssue creates new issue with labels for repository.
|
// NewIssue creates new issue with labels for repository.
|
||||||
|
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||||
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
|
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -399,6 +402,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, la
|
|||||||
}
|
}
|
||||||
|
|
||||||
issue.Index = idx
|
issue.Index = idx
|
||||||
|
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
|
||||||
|
|
||||||
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
|
@ -572,6 +572,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Iss
|
|||||||
}
|
}
|
||||||
|
|
||||||
issue.Index = idx
|
issue.Index = idx
|
||||||
|
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
|
||||||
|
|
||||||
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
@ -16,7 +15,6 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/testlogger"
|
"code.gitea.io/gitea/modules/testlogger"
|
||||||
|
|
||||||
@ -35,27 +33,7 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu
|
|||||||
ourSkip := 2
|
ourSkip := 2
|
||||||
ourSkip += skip
|
ourSkip += skip
|
||||||
deferFn := testlogger.PrintCurrentTest(t, ourSkip)
|
deferFn := testlogger.PrintCurrentTest(t, ourSkip)
|
||||||
assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
|
assert.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath))
|
||||||
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath))
|
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
|
||||||
if err != nil {
|
|
||||||
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
|
||||||
}
|
|
||||||
for _, ownerDir := range ownerDirs {
|
|
||||||
if !ownerDir.Type().IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
|
|
||||||
if err != nil {
|
|
||||||
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
|
|
||||||
}
|
|
||||||
for _, repoDir := range repoDirs {
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := deleteDB(); err != nil {
|
if err := deleteDB(); err != nil {
|
||||||
t.Errorf("unable to reset database: %v", err)
|
t.Errorf("unable to reset database: %v", err)
|
||||||
@ -112,39 +90,36 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MainTest(m *testing.M) {
|
func MainTest(m *testing.M) {
|
||||||
log.RegisterEventWriter("test", testlogger.NewTestLoggerWriter)
|
testlogger.Init()
|
||||||
|
|
||||||
giteaRoot := base.SetupGiteaRoot()
|
giteaRoot := base.SetupGiteaRoot()
|
||||||
if giteaRoot == "" {
|
if giteaRoot == "" {
|
||||||
fmt.Println("Environment variable $GITEA_ROOT not set")
|
testlogger.Fatalf("Environment variable $GITEA_ROOT not set\n")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
giteaBinary := "gitea"
|
giteaBinary := "gitea"
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
giteaBinary += ".exe"
|
giteaBinary += ".exe"
|
||||||
}
|
}
|
||||||
setting.AppPath = path.Join(giteaRoot, giteaBinary)
|
setting.AppPath = filepath.Join(giteaRoot, giteaBinary)
|
||||||
if _, err := os.Stat(setting.AppPath); err != nil {
|
if _, err := os.Stat(setting.AppPath); err != nil {
|
||||||
fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath)
|
testlogger.Fatalf("Could not find gitea binary at %s\n", setting.AppPath)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
giteaConf := os.Getenv("GITEA_CONF")
|
giteaConf := os.Getenv("GITEA_CONF")
|
||||||
if giteaConf == "" {
|
if giteaConf == "" {
|
||||||
giteaConf = path.Join(filepath.Dir(setting.AppPath), "tests/sqlite.ini")
|
giteaConf = filepath.Join(filepath.Dir(setting.AppPath), "tests/sqlite.ini")
|
||||||
fmt.Printf("Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
|
fmt.Printf("Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !path.IsAbs(giteaConf) {
|
if !filepath.IsAbs(giteaConf) {
|
||||||
setting.CustomConf = path.Join(giteaRoot, giteaConf)
|
setting.CustomConf = filepath.Join(giteaRoot, giteaConf)
|
||||||
} else {
|
} else {
|
||||||
setting.CustomConf = giteaConf
|
setting.CustomConf = giteaConf
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpDataPath, err := os.MkdirTemp("", "data")
|
tmpDataPath, err := os.MkdirTemp("", "data")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Unable to create temporary data path %v\n", err)
|
testlogger.Fatalf("Unable to create temporary data path %v\n", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
||||||
@ -152,8 +127,7 @@ func MainTest(m *testing.M) {
|
|||||||
|
|
||||||
unittest.InitSettings()
|
unittest.InitSettings()
|
||||||
if err = git.InitFull(context.Background()); err != nil {
|
if err = git.InitFull(context.Background()); err != nil {
|
||||||
fmt.Printf("Unable to InitFull: %v\n", err)
|
testlogger.Fatalf("Unable to InitFull: %v\n", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
setting.LoadDBSetting()
|
setting.LoadDBSetting()
|
||||||
setting.InitLoggersForTest()
|
setting.InitLoggersForTest()
|
||||||
|
@ -366,7 +366,8 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(306, "Add BlockAdminMergeOverride to ProtectedBranch", v1_23.AddBlockAdminMergeOverrideBranchProtection),
|
newMigration(306, "Add BlockAdminMergeOverride to ProtectedBranch", v1_23.AddBlockAdminMergeOverrideBranchProtection),
|
||||||
newMigration(307, "Fix milestone deadline_unix when there is no due date", v1_23.FixMilestoneNoDueDate),
|
newMigration(307, "Fix milestone deadline_unix when there is no due date", v1_23.FixMilestoneNoDueDate),
|
||||||
newMigration(308, "Add index(user_id, is_deleted) for action table", v1_23.AddNewIndexForUserDashboard),
|
newMigration(308, "Add index(user_id, is_deleted) for action table", v1_23.AddNewIndexForUserDashboard),
|
||||||
newMigration(309, "Add DeleteBranchAfterMerge to AutoMerge", v1_23.AddDeleteBranchAfterMergeForAutoMerge),
|
newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices),
|
||||||
|
newMigration(310, "Add DeleteBranchAfterMerge to AutoMerge", v1_23.AddDeleteBranchAfterMergeForAutoMerge),
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
giturl "code.gitea.io/gitea/modules/git/url"
|
giturl "code.gitea.io/gitea/modules/git/url"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
@ -163,7 +164,9 @@ func migratePushMirrors(x *xorm.Engine) error {
|
|||||||
|
|
||||||
func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
|
func getRemoteAddress(ownerName, repoName, remoteName string) (string, error) {
|
||||||
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(ownerName), strings.ToLower(repoName)+".git")
|
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(ownerName), strings.ToLower(repoName)+".git")
|
||||||
|
if exist, _ := util.IsExist(repoPath); !exist {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
remoteURL, err := git.GetRemoteAddress(context.Background(), repoPath, remoteName)
|
remoteURL, err := git.GetRemoteAddress(context.Background(), repoPath, remoteName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err)
|
return "", fmt.Errorf("get remote %s's address of %s/%s failed: %v", remoteName, ownerName, repoName, err)
|
||||||
|
@ -7,23 +7,71 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
"xorm.io/xorm/schemas"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pullAutoMerge struct {
|
type improveNotificationTableIndicesAction struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
PullID int64 `xorm:"UNIQUE"`
|
UserID int64 `xorm:"NOT NULL"`
|
||||||
DoerID int64 `xorm:"INDEX NOT NULL"`
|
RepoID int64 `xorm:"NOT NULL"`
|
||||||
MergeStyle string `xorm:"varchar(30)"`
|
|
||||||
Message string `xorm:"LONGTEXT"`
|
Status uint8 `xorm:"SMALLINT NOT NULL"`
|
||||||
DeleteBranchAfterMerge bool
|
Source uint8 `xorm:"SMALLINT NOT NULL"`
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
|
||||||
|
IssueID int64 `xorm:"NOT NULL"`
|
||||||
|
CommitID string
|
||||||
|
CommentID int64
|
||||||
|
|
||||||
|
UpdatedBy int64 `xorm:"NOT NULL"`
|
||||||
|
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
||||||
|
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName return database table name for xorm
|
// TableName sets the name of this table
|
||||||
func (pullAutoMerge) TableName() string {
|
func (*improveNotificationTableIndicesAction) TableName() string {
|
||||||
return "pull_auto_merge"
|
return "notification"
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddDeleteBranchAfterMergeForAutoMerge(x *xorm.Engine) error {
|
// TableIndices implements xorm's TableIndices interface
|
||||||
return x.Sync(new(pullAutoMerge))
|
func (*improveNotificationTableIndicesAction) TableIndices() []*schemas.Index {
|
||||||
|
indices := make([]*schemas.Index, 0, 8)
|
||||||
|
usuuIndex := schemas.NewIndex("u_s_uu", schemas.IndexType)
|
||||||
|
usuuIndex.AddColumn("user_id", "status", "updated_unix")
|
||||||
|
indices = append(indices, usuuIndex)
|
||||||
|
|
||||||
|
// Add the individual indices that were previously defined in struct tags
|
||||||
|
userIDIndex := schemas.NewIndex("idx_notification_user_id", schemas.IndexType)
|
||||||
|
userIDIndex.AddColumn("user_id")
|
||||||
|
indices = append(indices, userIDIndex)
|
||||||
|
|
||||||
|
repoIDIndex := schemas.NewIndex("idx_notification_repo_id", schemas.IndexType)
|
||||||
|
repoIDIndex.AddColumn("repo_id")
|
||||||
|
indices = append(indices, repoIDIndex)
|
||||||
|
|
||||||
|
statusIndex := schemas.NewIndex("idx_notification_status", schemas.IndexType)
|
||||||
|
statusIndex.AddColumn("status")
|
||||||
|
indices = append(indices, statusIndex)
|
||||||
|
|
||||||
|
sourceIndex := schemas.NewIndex("idx_notification_source", schemas.IndexType)
|
||||||
|
sourceIndex.AddColumn("source")
|
||||||
|
indices = append(indices, sourceIndex)
|
||||||
|
|
||||||
|
issueIDIndex := schemas.NewIndex("idx_notification_issue_id", schemas.IndexType)
|
||||||
|
issueIDIndex.AddColumn("issue_id")
|
||||||
|
indices = append(indices, issueIDIndex)
|
||||||
|
|
||||||
|
commitIDIndex := schemas.NewIndex("idx_notification_commit_id", schemas.IndexType)
|
||||||
|
commitIDIndex.AddColumn("commit_id")
|
||||||
|
indices = append(indices, commitIDIndex)
|
||||||
|
|
||||||
|
updatedByIndex := schemas.NewIndex("idx_notification_updated_by", schemas.IndexType)
|
||||||
|
updatedByIndex.AddColumn("updated_by")
|
||||||
|
indices = append(indices, updatedByIndex)
|
||||||
|
|
||||||
|
return indices
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImproveNotificationTableIndices(x *xorm.Engine) error {
|
||||||
|
return x.Sync(&improveNotificationTableIndicesAction{})
|
||||||
}
|
}
|
||||||
|
29
models/migrations/v1_23/v310.go
Normal file
29
models/migrations/v1_23/v310.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pullAutoMerge struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
PullID int64 `xorm:"UNIQUE"`
|
||||||
|
DoerID int64 `xorm:"INDEX NOT NULL"`
|
||||||
|
MergeStyle string `xorm:"varchar(30)"`
|
||||||
|
Message string `xorm:"LONGTEXT"`
|
||||||
|
DeleteBranchAfterMerge bool
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName return database table name for xorm
|
||||||
|
func (pullAutoMerge) TableName() string {
|
||||||
|
return "pull_auto_merge"
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddDeleteBranchAfterMergeForAutoMerge(x *xorm.Engine) error {
|
||||||
|
return x.Sync(new(pullAutoMerge))
|
||||||
|
}
|
@ -1,78 +0,0 @@
|
|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package organization
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
"code.gitea.io/gitea/models/unit"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MinimalOrg represents a simple organization with only the needed columns
|
|
||||||
type MinimalOrg = Organization
|
|
||||||
|
|
||||||
// GetUserOrgsList returns all organizations the given user has access to
|
|
||||||
func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, error) {
|
|
||||||
schema, err := db.TableInfo(new(user_model.User))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
outputCols := []string{
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"full_name",
|
|
||||||
"visibility",
|
|
||||||
"avatar",
|
|
||||||
"avatar_email",
|
|
||||||
"use_custom_avatar",
|
|
||||||
}
|
|
||||||
|
|
||||||
groupByCols := &strings.Builder{}
|
|
||||||
for _, col := range outputCols {
|
|
||||||
fmt.Fprintf(groupByCols, "`%s`.%s,", schema.Name, col)
|
|
||||||
}
|
|
||||||
groupByStr := groupByCols.String()
|
|
||||||
groupByStr = groupByStr[0 : len(groupByStr)-1]
|
|
||||||
|
|
||||||
sess := db.GetEngine(ctx)
|
|
||||||
sess = sess.Select(groupByStr+", count(distinct repo_id) as org_count").
|
|
||||||
Table("user").
|
|
||||||
Join("INNER", "team", "`team`.org_id = `user`.id").
|
|
||||||
Join("INNER", "team_user", "`team`.id = `team_user`.team_id").
|
|
||||||
Join("LEFT", builder.
|
|
||||||
Select("id as repo_id, owner_id as repo_owner_id").
|
|
||||||
From("repository").
|
|
||||||
Where(repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)), "`repository`.repo_owner_id = `team`.org_id").
|
|
||||||
Where("`team_user`.uid = ?", user.ID).
|
|
||||||
GroupBy(groupByStr)
|
|
||||||
|
|
||||||
type OrgCount struct {
|
|
||||||
Organization `xorm:"extends"`
|
|
||||||
OrgCount int
|
|
||||||
}
|
|
||||||
|
|
||||||
orgCounts := make([]*OrgCount, 0, 10)
|
|
||||||
|
|
||||||
if err := sess.
|
|
||||||
Asc("`user`.name").
|
|
||||||
Find(&orgCounts); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
orgs := make([]*MinimalOrg, len(orgCounts))
|
|
||||||
for i, orgCount := range orgCounts {
|
|
||||||
orgCount.Organization.NumRepos = orgCount.OrgCount
|
|
||||||
orgs[i] = &orgCount.Organization
|
|
||||||
}
|
|
||||||
|
|
||||||
return orgs, nil
|
|
||||||
}
|
|
@ -22,15 +22,9 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ________ .__ __ .__
|
|
||||||
// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
|
|
||||||
// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
|
|
||||||
// / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \
|
|
||||||
// \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| /
|
|
||||||
// \/ /_____/ \/ \/ \/ \/ \/
|
|
||||||
|
|
||||||
// ErrOrgNotExist represents a "OrgNotExist" kind of error.
|
// ErrOrgNotExist represents a "OrgNotExist" kind of error.
|
||||||
type ErrOrgNotExist struct {
|
type ErrOrgNotExist struct {
|
||||||
ID int64
|
ID int64
|
||||||
@ -141,8 +135,9 @@ func (org *Organization) LoadTeams(ctx context.Context) ([]*Team, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetMembers returns all members of organization.
|
// GetMembers returns all members of organization.
|
||||||
func (org *Organization) GetMembers(ctx context.Context) (user_model.UserList, map[int64]bool, error) {
|
func (org *Organization) GetMembers(ctx context.Context, doer *user_model.User) (user_model.UserList, map[int64]bool, error) {
|
||||||
return FindOrgMembers(ctx, &FindOrgMembersOpts{
|
return FindOrgMembers(ctx, &FindOrgMembersOpts{
|
||||||
|
Doer: doer,
|
||||||
OrgID: org.ID,
|
OrgID: org.ID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -195,16 +190,39 @@ func (org *Organization) CanCreateRepo() bool {
|
|||||||
// FindOrgMembersOpts represensts find org members conditions
|
// FindOrgMembersOpts represensts find org members conditions
|
||||||
type FindOrgMembersOpts struct {
|
type FindOrgMembersOpts struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
|
Doer *user_model.User
|
||||||
|
IsDoerMember bool
|
||||||
OrgID int64
|
OrgID int64
|
||||||
PublicOnly bool
|
}
|
||||||
|
|
||||||
|
func (opts FindOrgMembersOpts) PublicOnly() bool {
|
||||||
|
return opts.Doer == nil || !(opts.IsDoerMember || opts.Doer.IsAdmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyTeamMatesOnlyFilter make sure restricted users only see public team members and there own team mates
|
||||||
|
func (opts FindOrgMembersOpts) applyTeamMatesOnlyFilter(sess *xorm.Session) {
|
||||||
|
if opts.Doer != nil && opts.IsDoerMember && opts.Doer.IsRestricted {
|
||||||
|
teamMates := builder.Select("DISTINCT team_user.uid").
|
||||||
|
From("team_user").
|
||||||
|
Where(builder.In("team_user.team_id", getUserTeamIDsQueryBuilder(opts.OrgID, opts.Doer.ID))).
|
||||||
|
And(builder.Eq{"team_user.org_id": opts.OrgID})
|
||||||
|
|
||||||
|
sess.And(
|
||||||
|
builder.In("org_user.uid", teamMates).
|
||||||
|
Or(builder.Eq{"org_user.is_public": true}),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountOrgMembers counts the organization's members
|
// CountOrgMembers counts the organization's members
|
||||||
func CountOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (int64, error) {
|
func CountOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (int64, error) {
|
||||||
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
||||||
if opts.PublicOnly {
|
if opts.PublicOnly() {
|
||||||
sess.And("is_public = ?", true)
|
sess = sess.And("is_public = ?", true)
|
||||||
|
} else {
|
||||||
|
opts.applyTeamMatesOnlyFilter(sess)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess.Count(new(OrgUser))
|
return sess.Count(new(OrgUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,42 +458,6 @@ func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) (map[int64]*u
|
|||||||
And("team_user.org_id = ?", orgID).Find(&users)
|
And("team_user.org_id = ?", orgID).Find(&users)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchOrganizationsOptions options to filter organizations
|
|
||||||
type SearchOrganizationsOptions struct {
|
|
||||||
db.ListOptions
|
|
||||||
All bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindOrgOptions finds orgs options
|
|
||||||
type FindOrgOptions struct {
|
|
||||||
db.ListOptions
|
|
||||||
UserID int64
|
|
||||||
IncludePrivate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
|
|
||||||
cond := builder.Eq{"uid": userID}
|
|
||||||
if !includePrivate {
|
|
||||||
cond["is_public"] = true
|
|
||||||
}
|
|
||||||
return builder.Select("org_id").From("org_user").Where(cond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts FindOrgOptions) ToConds() builder.Cond {
|
|
||||||
var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization}
|
|
||||||
if opts.UserID > 0 {
|
|
||||||
cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate)))
|
|
||||||
}
|
|
||||||
if !opts.IncludePrivate {
|
|
||||||
cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
|
|
||||||
}
|
|
||||||
return cond
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts FindOrgOptions) ToOrders() string {
|
|
||||||
return "`user`.name ASC"
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasOrgOrUserVisible tells if the given user can see the given org or user
|
// HasOrgOrUserVisible tells if the given user can see the given org or user
|
||||||
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
||||||
// If user is nil, it's an anonymous user/request.
|
// If user is nil, it's an anonymous user/request.
|
||||||
@ -508,26 +490,15 @@ func HasOrgsVisible(ctx context.Context, orgs []*Organization, user *user_model.
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID
|
|
||||||
// are allowed to create repos.
|
|
||||||
func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) {
|
|
||||||
orgs := make([]*Organization, 0, 10)
|
|
||||||
|
|
||||||
return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`").
|
|
||||||
Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id").
|
|
||||||
Join("INNER", "`team`", "`team`.id = `team_user`.team_id").
|
|
||||||
Where(builder.Eq{"`team_user`.uid": userID}).
|
|
||||||
And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))).
|
|
||||||
Asc("`user`.name").
|
|
||||||
Find(&orgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrgUsersByOrgID returns all organization-user relations by organization ID.
|
// GetOrgUsersByOrgID returns all organization-user relations by organization ID.
|
||||||
func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) {
|
func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) {
|
||||||
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
||||||
if opts.PublicOnly {
|
if opts.PublicOnly() {
|
||||||
sess.And("is_public = ?", true)
|
sess = sess.And("is_public = ?", true)
|
||||||
|
} else {
|
||||||
|
opts.applyTeamMatesOnlyFilter(sess)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.ListOptions.PageSize > 0 {
|
if opts.ListOptions.PageSize > 0 {
|
||||||
sess = db.SetSessionPagination(sess, opts)
|
sess = db.SetSessionPagination(sess, opts)
|
||||||
|
|
||||||
@ -656,6 +627,15 @@ func (org *Organization) getUserTeamIDs(ctx context.Context, userID int64) ([]in
|
|||||||
Find(&teamIDs)
|
Find(&teamIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUserTeamIDsQueryBuilder(orgID, userID int64) *builder.Builder {
|
||||||
|
return builder.Select("team.id").From("team").
|
||||||
|
InnerJoin("team_user", "team_user.team_id = team.id").
|
||||||
|
Where(builder.Eq{
|
||||||
|
"team_user.org_id": orgID,
|
||||||
|
"team_user.uid": userID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TeamsWithAccessToRepo returns all teams that have given access level to the repository.
|
// TeamsWithAccessToRepo returns all teams that have given access level to the repository.
|
||||||
func (org *Organization) TeamsWithAccessToRepo(ctx context.Context, repoID int64, mode perm.AccessMode) ([]*Team, error) {
|
func (org *Organization) TeamsWithAccessToRepo(ctx context.Context, repoID int64, mode perm.AccessMode) ([]*Team, error) {
|
||||||
return GetTeamsWithAccessToRepo(ctx, org.ID, repoID, mode)
|
return GetTeamsWithAccessToRepo(ctx, org.ID, repoID, mode)
|
||||||
|
138
models/organization/org_list.go
Normal file
138
models/organization/org_list.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package organization
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SearchOrganizationsOptions options to filter organizations
|
||||||
|
type SearchOrganizationsOptions struct {
|
||||||
|
db.ListOptions
|
||||||
|
All bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOrgOptions finds orgs options
|
||||||
|
type FindOrgOptions struct {
|
||||||
|
db.ListOptions
|
||||||
|
UserID int64
|
||||||
|
IncludePrivate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
|
||||||
|
cond := builder.Eq{"uid": userID}
|
||||||
|
if !includePrivate {
|
||||||
|
cond["is_public"] = true
|
||||||
|
}
|
||||||
|
return builder.Select("org_id").From("org_user").Where(cond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts FindOrgOptions) ToConds() builder.Cond {
|
||||||
|
var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization}
|
||||||
|
if opts.UserID > 0 {
|
||||||
|
cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate)))
|
||||||
|
}
|
||||||
|
if !opts.IncludePrivate {
|
||||||
|
cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
|
||||||
|
}
|
||||||
|
return cond
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts FindOrgOptions) ToOrders() string {
|
||||||
|
return "`user`.lower_name ASC"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID
|
||||||
|
// are allowed to create repos.
|
||||||
|
func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) {
|
||||||
|
orgs := make([]*Organization, 0, 10)
|
||||||
|
|
||||||
|
return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`").
|
||||||
|
Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id").
|
||||||
|
Join("INNER", "`team`", "`team`.id = `team_user`.team_id").
|
||||||
|
Where(builder.Eq{"`team_user`.uid": userID}).
|
||||||
|
And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))).
|
||||||
|
Asc("`user`.name").
|
||||||
|
Find(&orgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinimalOrg represents a simple organization with only the needed columns
|
||||||
|
type MinimalOrg = Organization
|
||||||
|
|
||||||
|
// GetUserOrgsList returns all organizations the given user has access to
|
||||||
|
func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, error) {
|
||||||
|
schema, err := db.TableInfo(new(user_model.User))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outputCols := []string{
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"full_name",
|
||||||
|
"visibility",
|
||||||
|
"avatar",
|
||||||
|
"avatar_email",
|
||||||
|
"use_custom_avatar",
|
||||||
|
}
|
||||||
|
|
||||||
|
selectColumns := &strings.Builder{}
|
||||||
|
for i, col := range outputCols {
|
||||||
|
fmt.Fprintf(selectColumns, "`%s`.%s", schema.Name, col)
|
||||||
|
if i < len(outputCols)-1 {
|
||||||
|
selectColumns.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
columnsStr := selectColumns.String()
|
||||||
|
|
||||||
|
var orgs []*MinimalOrg
|
||||||
|
if err := db.GetEngine(ctx).Select(columnsStr).
|
||||||
|
Table("user").
|
||||||
|
Where(builder.In("`user`.`id`", queryUserOrgIDs(user.ID, true))).
|
||||||
|
Find(&orgs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type orgCount struct {
|
||||||
|
OrgID int64
|
||||||
|
RepoCount int
|
||||||
|
}
|
||||||
|
var orgCounts []orgCount
|
||||||
|
if err := db.GetEngine(ctx).
|
||||||
|
Select("owner_id AS org_id, COUNT(DISTINCT(repository.id)) as repo_count").
|
||||||
|
Table("repository").
|
||||||
|
Join("INNER", "org_user", "owner_id = org_user.org_id").
|
||||||
|
Where("org_user.uid = ?", user.ID).
|
||||||
|
And(builder.Or(
|
||||||
|
builder.Eq{"repository.is_private": false},
|
||||||
|
builder.In("repository.id", builder.Select("repo_id").From("team_repo").
|
||||||
|
InnerJoin("team_user", "team_user.team_id = team_repo.team_id").
|
||||||
|
Where(builder.Eq{"team_user.uid": user.ID})),
|
||||||
|
builder.In("repository.id", builder.Select("repo_id").From("collaboration").
|
||||||
|
Where(builder.Eq{"user_id": user.ID})),
|
||||||
|
)).
|
||||||
|
GroupBy("owner_id").Find(&orgCounts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
orgCountMap := make(map[int64]int, len(orgCounts))
|
||||||
|
for _, orgCount := range orgCounts {
|
||||||
|
orgCountMap[orgCount.OrgID] = orgCount.RepoCount
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, org := range orgs {
|
||||||
|
org.NumRepos = orgCountMap[org.ID]
|
||||||
|
}
|
||||||
|
|
||||||
|
return orgs, nil
|
||||||
|
}
|
62
models/organization/org_list_test.go
Normal file
62
models/organization/org_list_test.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package organization_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCountOrganizations(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expected, cnt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindOrgs(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||||
|
UserID: 4,
|
||||||
|
IncludePrivate: true,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, orgs, 1) {
|
||||||
|
assert.EqualValues(t, 3, orgs[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||||
|
UserID: 4,
|
||||||
|
IncludePrivate: false,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, orgs, 0)
|
||||||
|
|
||||||
|
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||||
|
UserID: 4,
|
||||||
|
IncludePrivate: true,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserOrgsList(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, orgs, 1) {
|
||||||
|
assert.EqualValues(t, 3, orgs[0].ID)
|
||||||
|
// repo_id: 3 is in the team, 32 is public, 5 is private with no team
|
||||||
|
assert.EqualValues(t, 2, orgs[0].NumRepos)
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,8 @@
|
|||||||
package organization_test
|
package organization_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@ -103,7 +105,7 @@ func TestUser_GetTeams(t *testing.T) {
|
|||||||
func TestUser_GetMembers(t *testing.T) {
|
func TestUser_GetMembers(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||||
members, _, err := org.GetMembers(db.DefaultContext)
|
members, _, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
if assert.Len(t, members, 3) {
|
if assert.Len(t, members, 3) {
|
||||||
assert.Equal(t, int64(2), members[0].ID)
|
assert.Equal(t, int64(2), members[0].ID)
|
||||||
@ -127,15 +129,6 @@ func TestGetOrgByName(t *testing.T) {
|
|||||||
assert.True(t, organization.IsErrOrgNotExist(err))
|
assert.True(t, organization.IsErrOrgNotExist(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCountOrganizations(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, expected, cnt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsOrganizationOwner(t *testing.T) {
|
func TestIsOrganizationOwner(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
test := func(orgID, userID int64, expected bool) {
|
test := func(orgID, userID int64, expected bool) {
|
||||||
@ -180,67 +173,114 @@ func TestIsPublicMembership(t *testing.T) {
|
|||||||
test(unittest.NonexistentID, unittest.NonexistentID, false)
|
test(unittest.NonexistentID, unittest.NonexistentID, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindOrgs(t *testing.T) {
|
func TestRestrictedUserOrgMembers(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{
|
||||||
UserID: 4,
|
ID: 29,
|
||||||
IncludePrivate: true,
|
IsRestricted: true,
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
if !assert.True(t, restrictedUser.IsRestricted) {
|
||||||
if assert.Len(t, orgs, 1) {
|
return // ensure fixtures return restricted user
|
||||||
assert.EqualValues(t, 3, orgs[0].ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
testCases := []struct {
|
||||||
UserID: 4,
|
name string
|
||||||
IncludePrivate: false,
|
opts *organization.FindOrgMembersOpts
|
||||||
})
|
expectedUIDs []int64
|
||||||
assert.NoError(t, err)
|
}{
|
||||||
assert.Len(t, orgs, 0)
|
{
|
||||||
|
name: "restricted user sees public members and teammates",
|
||||||
|
opts: &organization.FindOrgMembersOpts{
|
||||||
|
OrgID: 17, // org17 where user29 is in team9
|
||||||
|
Doer: restrictedUser,
|
||||||
|
IsDoerMember: true,
|
||||||
|
},
|
||||||
|
expectedUIDs: []int64{2, 15, 20, 29}, // Public members (2) + teammates in team9 (15, 20, 29)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "restricted user sees only public members when not member",
|
||||||
|
opts: &organization.FindOrgMembersOpts{
|
||||||
|
OrgID: 3, // org3 where user29 is not a member
|
||||||
|
Doer: restrictedUser,
|
||||||
|
},
|
||||||
|
expectedUIDs: []int64{2, 28}, // Only public members
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non logged in only shows public members",
|
||||||
|
opts: &organization.FindOrgMembersOpts{
|
||||||
|
OrgID: 3,
|
||||||
|
},
|
||||||
|
expectedUIDs: []int64{2, 28}, // Only public members
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non restricted user sees all members",
|
||||||
|
opts: &organization.FindOrgMembersOpts{
|
||||||
|
OrgID: 17,
|
||||||
|
Doer: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}),
|
||||||
|
IsDoerMember: true,
|
||||||
|
},
|
||||||
|
expectedUIDs: []int64{2, 15, 18, 20, 29}, // All members
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
for _, tc := range testCases {
|
||||||
UserID: 4,
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
IncludePrivate: true,
|
count, err := organization.CountOrgMembers(db.DefaultContext, tc.opts)
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 1, total)
|
assert.EqualValues(t, len(tc.expectedUIDs), count)
|
||||||
|
|
||||||
|
members, err := organization.GetOrgUsersByOrgID(db.DefaultContext, tc.opts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
memberUIDs := make([]int64, 0, len(members))
|
||||||
|
for _, member := range members {
|
||||||
|
memberUIDs = append(memberUIDs, member.UID)
|
||||||
|
}
|
||||||
|
slices.Sort(memberUIDs)
|
||||||
|
assert.EqualValues(t, tc.expectedUIDs, memberUIDs)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOrgUsersByOrgID(t *testing.T) {
|
func TestGetOrgUsersByOrgID(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
orgUsers, err := organization.GetOrgUsersByOrgID(db.DefaultContext, &organization.FindOrgMembersOpts{
|
opts := &organization.FindOrgMembersOpts{
|
||||||
ListOptions: db.ListOptions{},
|
Doer: &user_model.User{IsAdmin: true},
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
PublicOnly: false,
|
}
|
||||||
})
|
assert.False(t, opts.PublicOnly())
|
||||||
|
orgUsers, err := organization.GetOrgUsersByOrgID(db.DefaultContext, opts)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
if assert.Len(t, orgUsers, 3) {
|
sort.Slice(orgUsers, func(i, j int) bool {
|
||||||
assert.Equal(t, organization.OrgUser{
|
return orgUsers[i].ID < orgUsers[j].ID
|
||||||
ID: orgUsers[0].ID,
|
})
|
||||||
|
assert.EqualValues(t, []*organization.OrgUser{{
|
||||||
|
ID: 1,
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
UID: 2,
|
UID: 2,
|
||||||
IsPublic: true,
|
IsPublic: true,
|
||||||
}, *orgUsers[0])
|
}, {
|
||||||
assert.Equal(t, organization.OrgUser{
|
ID: 2,
|
||||||
ID: orgUsers[1].ID,
|
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
UID: 4,
|
UID: 4,
|
||||||
IsPublic: false,
|
IsPublic: false,
|
||||||
}, *orgUsers[1])
|
}, {
|
||||||
assert.Equal(t, organization.OrgUser{
|
ID: 9,
|
||||||
ID: orgUsers[2].ID,
|
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
UID: 28,
|
UID: 28,
|
||||||
IsPublic: true,
|
IsPublic: true,
|
||||||
}, *orgUsers[2])
|
}}, orgUsers)
|
||||||
}
|
|
||||||
|
opts = &organization.FindOrgMembersOpts{OrgID: 3}
|
||||||
|
assert.True(t, opts.PublicOnly())
|
||||||
|
orgUsers, err = organization.GetOrgUsersByOrgID(db.DefaultContext, opts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, orgUsers, 2)
|
||||||
|
|
||||||
orgUsers, err = organization.GetOrgUsersByOrgID(db.DefaultContext, &organization.FindOrgMembersOpts{
|
orgUsers, err = organization.GetOrgUsersByOrgID(db.DefaultContext, &organization.FindOrgMembersOpts{
|
||||||
ListOptions: db.ListOptions{},
|
ListOptions: db.ListOptions{},
|
||||||
OrgID: unittest.NonexistentID,
|
OrgID: unittest.NonexistentID,
|
||||||
PublicOnly: false,
|
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, orgUsers, 0)
|
assert.Len(t, orgUsers, 0)
|
||||||
|
@ -94,7 +94,7 @@ func TestUserListIsPublicMember(t *testing.T) {
|
|||||||
func testUserListIsPublicMember(t *testing.T, orgID int64, expected map[int64]bool) {
|
func testUserListIsPublicMember(t *testing.T, orgID int64, expected map[int64]bool) {
|
||||||
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, membersIsPublic, err := org.GetMembers(db.DefaultContext)
|
_, membersIsPublic, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, membersIsPublic)
|
assert.Equal(t, expected, membersIsPublic)
|
||||||
}
|
}
|
||||||
@ -121,7 +121,7 @@ func TestUserListIsUserOrgOwner(t *testing.T) {
|
|||||||
func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bool) {
|
func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bool) {
|
||||||
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
members, _, err := org.GetMembers(db.DefaultContext)
|
members, _, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, organization.IsUserOrgOwner(db.DefaultContext, members, orgID))
|
assert.Equal(t, expected, organization.IsUserOrgOwner(db.DefaultContext, members, orgID))
|
||||||
}
|
}
|
||||||
|
@ -242,6 +242,7 @@ func GetSearchOrderByBySortType(sortType string) db.SearchOrderBy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewProject creates a new Project
|
// NewProject creates a new Project
|
||||||
|
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||||
func NewProject(ctx context.Context, p *Project) error {
|
func NewProject(ctx context.Context, p *Project) error {
|
||||||
if !IsTemplateTypeValid(p.TemplateType) {
|
if !IsTemplateTypeValid(p.TemplateType) {
|
||||||
p.TemplateType = TemplateTypeNone
|
p.TemplateType = TemplateTypeNone
|
||||||
@ -255,6 +256,8 @@ func NewProject(ctx context.Context, p *Project) error {
|
|||||||
return util.NewInvalidArgumentErrorf("project type is not valid")
|
return util.NewInvalidArgumentErrorf("project type is not valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.Title, _ = util.SplitStringAtByteN(p.Title, 255)
|
||||||
|
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
if err := db.Insert(ctx, p); err != nil {
|
if err := db.Insert(ctx, p); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -308,6 +311,7 @@ func UpdateProject(ctx context.Context, p *Project) error {
|
|||||||
p.CardType = CardTypeTextOnly
|
p.CardType = CardTypeTextOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.Title, _ = util.SplitStringAtByteN(p.Title, 255)
|
||||||
_, err := db.GetEngine(ctx).ID(p.ID).Cols(
|
_, err := db.GetEngine(ctx).ID(p.ID).Cols(
|
||||||
"title",
|
"title",
|
||||||
"description",
|
"description",
|
||||||
|
@ -54,21 +54,6 @@ func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error)
|
|||||||
return &forkedRepo, nil
|
return &forkedRepo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetForks returns all the forks of the repository
|
|
||||||
func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions) ([]*Repository, error) {
|
|
||||||
sess := db.GetEngine(ctx)
|
|
||||||
|
|
||||||
var forks []*Repository
|
|
||||||
if listOptions.Page == 0 {
|
|
||||||
forks = make([]*Repository, 0, repo.NumForks)
|
|
||||||
} else {
|
|
||||||
forks = make([]*Repository, 0, listOptions.PageSize)
|
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
return forks, sess.Find(&forks, &Repository{ForkID: repo.ID})
|
|
||||||
}
|
|
||||||
|
|
||||||
// IncrementRepoForkNum increment repository fork number
|
// IncrementRepoForkNum increment repository fork number
|
||||||
func IncrementRepoForkNum(ctx context.Context, repoID int64) error {
|
func IncrementRepoForkNum(ctx context.Context, repoID int64) error {
|
||||||
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID)
|
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID)
|
||||||
|
@ -9,15 +9,13 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrPushMirrorNotExist mirror does not exist error
|
|
||||||
var ErrPushMirrorNotExist = util.NewNotExistErrorf("PushMirror does not exist")
|
|
||||||
|
|
||||||
// PushMirror represents mirror information of a repository.
|
// PushMirror represents mirror information of a repository.
|
||||||
type PushMirror struct {
|
type PushMirror struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
@ -96,26 +94,46 @@ func DeletePushMirrors(ctx context.Context, opts PushMirrorOptions) error {
|
|||||||
return util.NewInvalidArgumentErrorf("repoID required and must be set")
|
return util.NewInvalidArgumentErrorf("repoID required and must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type findPushMirrorOptions struct {
|
||||||
|
db.ListOptions
|
||||||
|
RepoID int64
|
||||||
|
SyncOnCommit optional.Option[bool]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts findPushMirrorOptions) ToConds() builder.Cond {
|
||||||
|
cond := builder.NewCond()
|
||||||
|
if opts.RepoID > 0 {
|
||||||
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
|
}
|
||||||
|
if opts.SyncOnCommit.Has() {
|
||||||
|
cond = cond.And(builder.Eq{"sync_on_commit": opts.SyncOnCommit.Value()})
|
||||||
|
}
|
||||||
|
return cond
|
||||||
|
}
|
||||||
|
|
||||||
// GetPushMirrorsByRepoID returns push-mirror information of a repository.
|
// GetPushMirrorsByRepoID returns push-mirror information of a repository.
|
||||||
func GetPushMirrorsByRepoID(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*PushMirror, int64, error) {
|
func GetPushMirrorsByRepoID(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*PushMirror, int64, error) {
|
||||||
sess := db.GetEngine(ctx).Where("repo_id = ?", repoID)
|
return db.FindAndCount[PushMirror](ctx, findPushMirrorOptions{
|
||||||
if listOptions.Page != 0 {
|
ListOptions: listOptions,
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
RepoID: repoID,
|
||||||
mirrors := make([]*PushMirror, 0, listOptions.PageSize)
|
})
|
||||||
count, err := sess.FindAndCount(&mirrors)
|
|
||||||
return mirrors, count, err
|
|
||||||
}
|
}
|
||||||
mirrors := make([]*PushMirror, 0, 10)
|
|
||||||
count, err := sess.FindAndCount(&mirrors)
|
func GetPushMirrorByIDAndRepoID(ctx context.Context, id, repoID int64) (*PushMirror, bool, error) {
|
||||||
return mirrors, count, err
|
var pushMirror PushMirror
|
||||||
|
has, err := db.GetEngine(ctx).Where("id = ?", id).And("repo_id = ?", repoID).Get(&pushMirror)
|
||||||
|
if !has || err != nil {
|
||||||
|
return nil, has, err
|
||||||
|
}
|
||||||
|
return &pushMirror, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPushMirrorsSyncedOnCommit returns push-mirrors for this repo that should be updated by new commits
|
// GetPushMirrorsSyncedOnCommit returns push-mirrors for this repo that should be updated by new commits
|
||||||
func GetPushMirrorsSyncedOnCommit(ctx context.Context, repoID int64) ([]*PushMirror, error) {
|
func GetPushMirrorsSyncedOnCommit(ctx context.Context, repoID int64) ([]*PushMirror, error) {
|
||||||
mirrors := make([]*PushMirror, 0, 10)
|
return db.Find[PushMirror](ctx, findPushMirrorOptions{
|
||||||
return mirrors, db.GetEngine(ctx).
|
RepoID: repoID,
|
||||||
Where("repo_id = ? AND sync_on_commit = ?", repoID, true).
|
SyncOnCommit: optional.Some(true),
|
||||||
Find(&mirrors)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushMirrorsIterate iterates all push-mirror repositories.
|
// PushMirrorsIterate iterates all push-mirror repositories.
|
||||||
|
@ -156,6 +156,7 @@ func IsReleaseExist(ctx context.Context, repoID int64, tagName string) (bool, er
|
|||||||
|
|
||||||
// UpdateRelease updates all columns of a release
|
// UpdateRelease updates all columns of a release
|
||||||
func UpdateRelease(ctx context.Context, rel *Release) error {
|
func UpdateRelease(ctx context.Context, rel *Release) error {
|
||||||
|
rel.Title, _ = util.SplitStringAtByteN(rel.Title, 255)
|
||||||
_, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel)
|
_, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -165,8 +166,8 @@ type Repository struct {
|
|||||||
|
|
||||||
Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
|
Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
|
||||||
|
|
||||||
RenderingMetas map[string]string `xorm:"-"`
|
commonRenderingMetas map[string]string `xorm:"-"`
|
||||||
DocumentRenderingMetas map[string]string `xorm:"-"`
|
|
||||||
Units []*RepoUnit `xorm:"-"`
|
Units []*RepoUnit `xorm:"-"`
|
||||||
PrimaryLanguage *LanguageStat `xorm:"-"`
|
PrimaryLanguage *LanguageStat `xorm:"-"`
|
||||||
|
|
||||||
@ -473,13 +474,11 @@ func (repo *Repository) MustOwner(ctx context.Context) *user_model.User {
|
|||||||
return repo.Owner
|
return repo.Owner
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers.
|
func (repo *Repository) composeCommonMetas(ctx context.Context) map[string]string {
|
||||||
func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
|
if len(repo.commonRenderingMetas) == 0 {
|
||||||
if len(repo.RenderingMetas) == 0 {
|
|
||||||
metas := map[string]string{
|
metas := map[string]string{
|
||||||
"user": repo.OwnerName,
|
"user": repo.OwnerName,
|
||||||
"repo": repo.Name,
|
"repo": repo.Name,
|
||||||
"mode": "comment",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unit, err := repo.GetUnit(ctx, unit.TypeExternalTracker)
|
unit, err := repo.GetUnit(ctx, unit.TypeExternalTracker)
|
||||||
@ -509,22 +508,34 @@ func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
|
|||||||
metas["org"] = strings.ToLower(repo.OwnerName)
|
metas["org"] = strings.ToLower(repo.OwnerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.RenderingMetas = metas
|
repo.commonRenderingMetas = metas
|
||||||
}
|
}
|
||||||
return repo.RenderingMetas
|
return repo.commonRenderingMetas
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComposeDocumentMetas composes a map of metas for properly rendering documents
|
// ComposeMetas composes a map of metas for properly rendering comments or comment-like contents (commit message)
|
||||||
|
func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
|
||||||
|
metas := maps.Clone(repo.composeCommonMetas(ctx))
|
||||||
|
metas["markdownLineBreakStyle"] = "comment"
|
||||||
|
metas["markupAllowShortIssuePattern"] = "true"
|
||||||
|
return metas
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComposeWikiMetas composes a map of metas for properly rendering wikis
|
||||||
|
func (repo *Repository) ComposeWikiMetas(ctx context.Context) map[string]string {
|
||||||
|
// does wiki need the "teams" and "org" from common metas?
|
||||||
|
metas := maps.Clone(repo.composeCommonMetas(ctx))
|
||||||
|
metas["markdownLineBreakStyle"] = "document"
|
||||||
|
metas["markupAllowShortIssuePattern"] = "true"
|
||||||
|
return metas
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComposeDocumentMetas composes a map of metas for properly rendering documents (repo files)
|
||||||
func (repo *Repository) ComposeDocumentMetas(ctx context.Context) map[string]string {
|
func (repo *Repository) ComposeDocumentMetas(ctx context.Context) map[string]string {
|
||||||
if len(repo.DocumentRenderingMetas) == 0 {
|
// does document(file) need the "teams" and "org" from common metas?
|
||||||
metas := map[string]string{}
|
metas := maps.Clone(repo.composeCommonMetas(ctx))
|
||||||
for k, v := range repo.ComposeMetas(ctx) {
|
metas["markdownLineBreakStyle"] = "document"
|
||||||
metas[k] = v
|
return metas
|
||||||
}
|
|
||||||
metas["mode"] = "document"
|
|
||||||
repo.DocumentRenderingMetas = metas
|
|
||||||
}
|
|
||||||
return repo.DocumentRenderingMetas
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBaseRepo populates repo.BaseRepo for a fork repository and
|
// GetBaseRepo populates repo.BaseRepo for a fork repository and
|
||||||
|
@ -98,8 +98,7 @@ func (repos RepositoryList) IDs() []int64 {
|
|||||||
return repoIDs
|
return repoIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAttributes loads the attributes for the given RepositoryList
|
func (repos RepositoryList) LoadOwners(ctx context.Context) error {
|
||||||
func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
|
||||||
if len(repos) == 0 {
|
if len(repos) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -107,10 +106,6 @@ func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
|||||||
userIDs := container.FilterSlice(repos, func(repo *Repository) (int64, bool) {
|
userIDs := container.FilterSlice(repos, func(repo *Repository) (int64, bool) {
|
||||||
return repo.OwnerID, true
|
return repo.OwnerID, true
|
||||||
})
|
})
|
||||||
repoIDs := make([]int64, len(repos))
|
|
||||||
for i := range repos {
|
|
||||||
repoIDs[i] = repos[i].ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load owners.
|
// Load owners.
|
||||||
users := make(map[int64]*user_model.User, len(userIDs))
|
users := make(map[int64]*user_model.User, len(userIDs))
|
||||||
@ -123,12 +118,19 @@ func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
|||||||
for i := range repos {
|
for i := range repos {
|
||||||
repos[i].Owner = users[repos[i].OwnerID]
|
repos[i].Owner = users[repos[i].OwnerID]
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repos RepositoryList) LoadLanguageStats(ctx context.Context) error {
|
||||||
|
if len(repos) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Load primary language.
|
// Load primary language.
|
||||||
stats := make(LanguageStatList, 0, len(repos))
|
stats := make(LanguageStatList, 0, len(repos))
|
||||||
if err := db.GetEngine(ctx).
|
if err := db.GetEngine(ctx).
|
||||||
Where("`is_primary` = ? AND `language` != ?", true, "other").
|
Where("`is_primary` = ? AND `language` != ?", true, "other").
|
||||||
In("`repo_id`", repoIDs).
|
In("`repo_id`", repos.IDs()).
|
||||||
Find(&stats); err != nil {
|
Find(&stats); err != nil {
|
||||||
return fmt.Errorf("find primary languages: %w", err)
|
return fmt.Errorf("find primary languages: %w", err)
|
||||||
}
|
}
|
||||||
@ -141,10 +143,18 @@ func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadAttributes loads the attributes for the given RepositoryList
|
||||||
|
func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
||||||
|
if err := repos.LoadOwners(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return repos.LoadLanguageStats(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// SearchRepoOptions holds the search options
|
// SearchRepoOptions holds the search options
|
||||||
type SearchRepoOptions struct {
|
type SearchRepoOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package repo_test
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@ -20,18 +19,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
countRepospts = repo_model.CountRepositoryOptions{OwnerID: 10}
|
countRepospts = CountRepositoryOptions{OwnerID: 10}
|
||||||
countReposptsPublic = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(false)}
|
countReposptsPublic = CountRepositoryOptions{OwnerID: 10, Private: optional.Some(false)}
|
||||||
countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: optional.Some(true)}
|
countReposptsPrivate = CountRepositoryOptions{OwnerID: 10, Private: optional.Some(true)}
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetRepositoryCount(t *testing.T) {
|
func TestGetRepositoryCount(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
ctx := db.DefaultContext
|
ctx := db.DefaultContext
|
||||||
count, err1 := repo_model.CountRepositories(ctx, countRepospts)
|
count, err1 := CountRepositories(ctx, countRepospts)
|
||||||
privateCount, err2 := repo_model.CountRepositories(ctx, countReposptsPrivate)
|
privateCount, err2 := CountRepositories(ctx, countReposptsPrivate)
|
||||||
publicCount, err3 := repo_model.CountRepositories(ctx, countReposptsPublic)
|
publicCount, err3 := CountRepositories(ctx, countReposptsPublic)
|
||||||
assert.NoError(t, err1)
|
assert.NoError(t, err1)
|
||||||
assert.NoError(t, err2)
|
assert.NoError(t, err2)
|
||||||
assert.NoError(t, err3)
|
assert.NoError(t, err3)
|
||||||
@ -42,7 +41,7 @@ func TestGetRepositoryCount(t *testing.T) {
|
|||||||
func TestGetPublicRepositoryCount(t *testing.T) {
|
func TestGetPublicRepositoryCount(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPublic)
|
count, err := CountRepositories(db.DefaultContext, countReposptsPublic)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(1), count)
|
assert.Equal(t, int64(1), count)
|
||||||
}
|
}
|
||||||
@ -50,14 +49,14 @@ func TestGetPublicRepositoryCount(t *testing.T) {
|
|||||||
func TestGetPrivateRepositoryCount(t *testing.T) {
|
func TestGetPrivateRepositoryCount(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPrivate)
|
count, err := CountRepositories(db.DefaultContext, countReposptsPrivate)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(2), count)
|
assert.Equal(t, int64(2), count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepoAPIURL(t *testing.T) {
|
func TestRepoAPIURL(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
|
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 10})
|
||||||
|
|
||||||
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL())
|
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL())
|
||||||
}
|
}
|
||||||
@ -65,22 +64,22 @@ func TestRepoAPIURL(t *testing.T) {
|
|||||||
func TestWatchRepo(t *testing.T) {
|
func TestWatchRepo(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 3})
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, user, repo, true))
|
assert.NoError(t, WatchRepo(db.DefaultContext, user, repo, true))
|
||||||
unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{RepoID: repo.ID, UserID: user.ID})
|
unittest.AssertExistsAndLoadBean(t, &Watch{RepoID: repo.ID, UserID: user.ID})
|
||||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
|
unittest.CheckConsistencyFor(t, &Repository{ID: repo.ID})
|
||||||
|
|
||||||
assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, user, repo, false))
|
assert.NoError(t, WatchRepo(db.DefaultContext, user, repo, false))
|
||||||
unittest.AssertNotExistsBean(t, &repo_model.Watch{RepoID: repo.ID, UserID: user.ID})
|
unittest.AssertNotExistsBean(t, &Watch{RepoID: repo.ID, UserID: user.ID})
|
||||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
|
unittest.CheckConsistencyFor(t, &Repository{ID: repo.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMetas(t *testing.T) {
|
func TestMetas(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
repo := &repo_model.Repository{Name: "testRepo"}
|
repo := &Repository{Name: "testRepo"}
|
||||||
repo.Owner = &user_model.User{Name: "testOwner"}
|
repo.Owner = &user_model.User{Name: "testOwner"}
|
||||||
repo.OwnerName = repo.Owner.Name
|
repo.OwnerName = repo.Owner.Name
|
||||||
|
|
||||||
@ -90,16 +89,16 @@ func TestMetas(t *testing.T) {
|
|||||||
assert.Equal(t, "testRepo", metas["repo"])
|
assert.Equal(t, "testRepo", metas["repo"])
|
||||||
assert.Equal(t, "testOwner", metas["user"])
|
assert.Equal(t, "testOwner", metas["user"])
|
||||||
|
|
||||||
externalTracker := repo_model.RepoUnit{
|
externalTracker := RepoUnit{
|
||||||
Type: unit.TypeExternalTracker,
|
Type: unit.TypeExternalTracker,
|
||||||
Config: &repo_model.ExternalTrackerConfig{
|
Config: &ExternalTrackerConfig{
|
||||||
ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
|
ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testSuccess := func(expectedStyle string) {
|
testSuccess := func(expectedStyle string) {
|
||||||
repo.Units = []*repo_model.RepoUnit{&externalTracker}
|
repo.Units = []*RepoUnit{&externalTracker}
|
||||||
repo.RenderingMetas = nil
|
repo.commonRenderingMetas = nil
|
||||||
metas := repo.ComposeMetas(db.DefaultContext)
|
metas := repo.ComposeMetas(db.DefaultContext)
|
||||||
assert.Equal(t, expectedStyle, metas["style"])
|
assert.Equal(t, expectedStyle, metas["style"])
|
||||||
assert.Equal(t, "testRepo", metas["repo"])
|
assert.Equal(t, "testRepo", metas["repo"])
|
||||||
@ -118,7 +117,7 @@ func TestMetas(t *testing.T) {
|
|||||||
externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp
|
externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp
|
||||||
testSuccess(markup.IssueNameStyleRegexp)
|
testSuccess(markup.IssueNameStyleRegexp)
|
||||||
|
|
||||||
repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 3)
|
repo, err := GetRepositoryByID(db.DefaultContext, 3)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
metas = repo.ComposeMetas(db.DefaultContext)
|
metas = repo.ComposeMetas(db.DefaultContext)
|
||||||
@ -132,7 +131,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
|||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
t.Run("InvalidPath", func(t *testing.T) {
|
t.Run("InvalidPath", func(t *testing.T) {
|
||||||
repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, "something")
|
repo, err := GetRepositoryByURL(db.DefaultContext, "something")
|
||||||
|
|
||||||
assert.Nil(t, repo)
|
assert.Nil(t, repo)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
@ -140,7 +139,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("ValidHttpURL", func(t *testing.T) {
|
t.Run("ValidHttpURL", func(t *testing.T) {
|
||||||
test := func(t *testing.T, url string) {
|
test := func(t *testing.T, url string) {
|
||||||
repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url)
|
repo, err := GetRepositoryByURL(db.DefaultContext, url)
|
||||||
|
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -155,7 +154,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("ValidGitSshURL", func(t *testing.T) {
|
t.Run("ValidGitSshURL", func(t *testing.T) {
|
||||||
test := func(t *testing.T, url string) {
|
test := func(t *testing.T, url string) {
|
||||||
repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url)
|
repo, err := GetRepositoryByURL(db.DefaultContext, url)
|
||||||
|
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -173,7 +172,7 @@ func TestGetRepositoryByURL(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("ValidImplicitSshURL", func(t *testing.T) {
|
t.Run("ValidImplicitSshURL", func(t *testing.T) {
|
||||||
test := func(t *testing.T, url string) {
|
test := func(t *testing.T, url string) {
|
||||||
repo, err := repo_model.GetRepositoryByURL(db.DefaultContext, url)
|
repo, err := GetRepositoryByURL(db.DefaultContext, url)
|
||||||
|
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -200,21 +199,21 @@ func TestComposeSSHCloneURL(t *testing.T) {
|
|||||||
setting.SSH.Domain = "domain"
|
setting.SSH.Domain = "domain"
|
||||||
setting.SSH.Port = 22
|
setting.SSH.Port = 22
|
||||||
setting.Repository.UseCompatSSHURI = false
|
setting.Repository.UseCompatSSHURI = false
|
||||||
assert.Equal(t, "git@domain:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "git@domain:user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
setting.Repository.UseCompatSSHURI = true
|
setting.Repository.UseCompatSSHURI = true
|
||||||
assert.Equal(t, "ssh://git@domain/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "ssh://git@domain/user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
// test SSH_DOMAIN while use non-standard SSH port
|
// test SSH_DOMAIN while use non-standard SSH port
|
||||||
setting.SSH.Port = 123
|
setting.SSH.Port = 123
|
||||||
setting.Repository.UseCompatSSHURI = false
|
setting.Repository.UseCompatSSHURI = false
|
||||||
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
setting.Repository.UseCompatSSHURI = true
|
setting.Repository.UseCompatSSHURI = true
|
||||||
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "ssh://git@domain:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
|
|
||||||
// test IPv6 SSH_DOMAIN
|
// test IPv6 SSH_DOMAIN
|
||||||
setting.Repository.UseCompatSSHURI = false
|
setting.Repository.UseCompatSSHURI = false
|
||||||
setting.SSH.Domain = "::1"
|
setting.SSH.Domain = "::1"
|
||||||
setting.SSH.Port = 22
|
setting.SSH.Port = 22
|
||||||
assert.Equal(t, "git@[::1]:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "git@[::1]:user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
setting.SSH.Port = 123
|
setting.SSH.Port = 123
|
||||||
assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
|
assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", ComposeSSHCloneURL("user", "repo"))
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ var OrderByMap = map[string]map[string]db.SearchOrderBy{
|
|||||||
var OrderByFlatMap = map[string]db.SearchOrderBy{
|
var OrderByFlatMap = map[string]db.SearchOrderBy{
|
||||||
"newest": OrderByMap["desc"]["created"],
|
"newest": OrderByMap["desc"]["created"],
|
||||||
"oldest": OrderByMap["asc"]["created"],
|
"oldest": OrderByMap["asc"]["created"],
|
||||||
|
"recentupdate": OrderByMap["desc"]["updated"],
|
||||||
"leastupdate": OrderByMap["asc"]["updated"],
|
"leastupdate": OrderByMap["asc"]["updated"],
|
||||||
"reversealphabetically": OrderByMap["desc"]["alpha"],
|
"reversealphabetically": OrderByMap["desc"]["alpha"],
|
||||||
"alphabetically": OrderByMap["asc"]["alpha"],
|
"alphabetically": OrderByMap["asc"]["alpha"],
|
||||||
|
@ -80,6 +80,27 @@ var (
|
|||||||
TypePullRequests,
|
TypePullRequests,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultMirrorRepoUnits contains the default unit types for mirrors
|
||||||
|
DefaultMirrorRepoUnits = []Type{
|
||||||
|
TypeCode,
|
||||||
|
TypeIssues,
|
||||||
|
TypeReleases,
|
||||||
|
TypeWiki,
|
||||||
|
TypeProjects,
|
||||||
|
TypePackages,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTemplateRepoUnits contains the default unit types for templates
|
||||||
|
DefaultTemplateRepoUnits = []Type{
|
||||||
|
TypeCode,
|
||||||
|
TypeIssues,
|
||||||
|
TypePullRequests,
|
||||||
|
TypeReleases,
|
||||||
|
TypeWiki,
|
||||||
|
TypeProjects,
|
||||||
|
TypePackages,
|
||||||
|
}
|
||||||
|
|
||||||
// NotAllowedDefaultRepoUnits contains units that can't be default
|
// NotAllowedDefaultRepoUnits contains units that can't be default
|
||||||
NotAllowedDefaultRepoUnits = []Type{
|
NotAllowedDefaultRepoUnits = []Type{
|
||||||
TypeExternalWiki,
|
TypeExternalWiki,
|
||||||
@ -147,6 +168,7 @@ func LoadUnitConfig() error {
|
|||||||
if len(DefaultRepoUnits) == 0 {
|
if len(DefaultRepoUnits) == 0 {
|
||||||
return errors.New("no default repository units found")
|
return errors.New("no default repository units found")
|
||||||
}
|
}
|
||||||
|
// default fork repo units
|
||||||
setDefaultForkRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultForkRepoUnits...)
|
setDefaultForkRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultForkRepoUnits...)
|
||||||
if len(invalidKeys) > 0 {
|
if len(invalidKeys) > 0 {
|
||||||
log.Warn("Invalid keys in default fork repo units: %s", strings.Join(invalidKeys, ", "))
|
log.Warn("Invalid keys in default fork repo units: %s", strings.Join(invalidKeys, ", "))
|
||||||
@ -155,6 +177,24 @@ func LoadUnitConfig() error {
|
|||||||
if len(DefaultForkRepoUnits) == 0 {
|
if len(DefaultForkRepoUnits) == 0 {
|
||||||
return errors.New("no default fork repository units found")
|
return errors.New("no default fork repository units found")
|
||||||
}
|
}
|
||||||
|
// default mirror repo units
|
||||||
|
setDefaultMirrorRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultMirrorRepoUnits...)
|
||||||
|
if len(invalidKeys) > 0 {
|
||||||
|
log.Warn("Invalid keys in default mirror repo units: %s", strings.Join(invalidKeys, ", "))
|
||||||
|
}
|
||||||
|
DefaultMirrorRepoUnits = validateDefaultRepoUnits(DefaultMirrorRepoUnits, setDefaultMirrorRepoUnits)
|
||||||
|
if len(DefaultMirrorRepoUnits) == 0 {
|
||||||
|
return errors.New("no default mirror repository units found")
|
||||||
|
}
|
||||||
|
// default template repo units
|
||||||
|
setDefaultTemplateRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultTemplateRepoUnits...)
|
||||||
|
if len(invalidKeys) > 0 {
|
||||||
|
log.Warn("Invalid keys in default template repo units: %s", strings.Join(invalidKeys, ", "))
|
||||||
|
}
|
||||||
|
DefaultTemplateRepoUnits = validateDefaultRepoUnits(DefaultTemplateRepoUnits, setDefaultTemplateRepoUnits)
|
||||||
|
if len(DefaultTemplateRepoUnits) == 0 {
|
||||||
|
return errors.New("no default template repository units found")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,8 @@
|
|||||||
package unittest
|
package unittest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -32,67 +30,73 @@ func Copy(src, dest string) error {
|
|||||||
return os.Symlink(target, dest)
|
return os.Symlink(target, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
sr, err := os.Open(src)
|
return util.CopyFile(src, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync synchronizes the two files. This is skipped if both files
|
||||||
|
// exist and the size, modtime, and mode match.
|
||||||
|
func Sync(srcPath, destPath string) error {
|
||||||
|
dest, err := os.Stat(destPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return Copy(srcPath, destPath)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := os.Stat(srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer sr.Close()
|
|
||||||
|
|
||||||
dw, err := os.Create(dest)
|
if src.Size() == dest.Size() &&
|
||||||
if err != nil {
|
src.ModTime() == dest.ModTime() &&
|
||||||
return err
|
src.Mode() == dest.Mode() {
|
||||||
}
|
return nil
|
||||||
defer dw.Close()
|
|
||||||
|
|
||||||
if _, err = io.Copy(dw, sr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set back file information.
|
return Copy(srcPath, destPath)
|
||||||
if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Chmod(dest, si.Mode())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyDir copy files recursively from source to target directory.
|
// SyncDirs synchronizes files recursively from source to target directory.
|
||||||
//
|
|
||||||
// The filter accepts a function that process the path info.
|
|
||||||
// and should return true for need to filter.
|
|
||||||
//
|
|
||||||
// It returns error when error occurs in underlying functions.
|
// It returns error when error occurs in underlying functions.
|
||||||
func CopyDir(srcPath, destPath string, filters ...func(filePath string) bool) error {
|
func SyncDirs(srcPath, destPath string) error {
|
||||||
// Check if target directory exists.
|
|
||||||
if _, err := os.Stat(destPath); !errors.Is(err, os.ErrNotExist) {
|
|
||||||
return util.NewAlreadyExistErrorf("file or directory already exists: %s", destPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := os.MkdirAll(destPath, os.ModePerm)
|
err := os.MkdirAll(destPath, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather directory info.
|
// find and delete all untracked files
|
||||||
infos, err := util.StatDir(srcPath, true)
|
destFiles, err := util.StatDir(destPath, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for _, destFile := range destFiles {
|
||||||
var filter func(filePath string) bool
|
destFilePath := filepath.Join(destPath, destFile)
|
||||||
if len(filters) > 0 {
|
if _, err = os.Stat(filepath.Join(srcPath, destFile)); err != nil {
|
||||||
filter = filters[0]
|
if os.IsNotExist(err) {
|
||||||
|
// if src file does not exist, remove dest file
|
||||||
|
if err = os.RemoveAll(destFilePath); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, info := range infos {
|
|
||||||
if filter != nil && filter(info) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
curPath := path.Join(destPath, info)
|
|
||||||
if strings.HasSuffix(info, "/") {
|
|
||||||
err = os.MkdirAll(curPath, os.ModePerm)
|
|
||||||
} else {
|
} else {
|
||||||
err = Copy(path.Join(srcPath, info), curPath)
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync src files to dest
|
||||||
|
srcFiles, err := util.StatDir(srcPath, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, srcFile := range srcFiles {
|
||||||
|
destFilePath := filepath.Join(destPath, srcFile)
|
||||||
|
// util.StatDir appends a slash to the directory name
|
||||||
|
if strings.HasSuffix(srcFile, "/") {
|
||||||
|
err = os.MkdirAll(destFilePath, os.ModePerm)
|
||||||
|
} else {
|
||||||
|
err = Sync(filepath.Join(srcPath, srcFile), destFilePath)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -164,35 +164,13 @@ func MainTest(m *testing.M, testOpts ...*TestOptions) {
|
|||||||
if err = storage.Init(); err != nil {
|
if err = storage.Init(); err != nil {
|
||||||
fatalTestError("storage.Init: %v\n", err)
|
fatalTestError("storage.Init: %v\n", err)
|
||||||
}
|
}
|
||||||
if err = util.RemoveAll(repoRootPath); err != nil {
|
if err = SyncDirs(filepath.Join(giteaRoot, "tests", "gitea-repositories-meta"), setting.RepoRootPath); err != nil {
|
||||||
fatalTestError("util.RemoveAll: %v\n", err)
|
fatalTestError("util.SyncDirs: %v\n", err)
|
||||||
}
|
|
||||||
if err = CopyDir(filepath.Join(giteaRoot, "tests", "gitea-repositories-meta"), setting.RepoRootPath); err != nil {
|
|
||||||
fatalTestError("util.CopyDir: %v\n", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = git.InitFull(context.Background()); err != nil {
|
if err = git.InitFull(context.Background()); err != nil {
|
||||||
fatalTestError("git.Init: %v\n", err)
|
fatalTestError("git.Init: %v\n", err)
|
||||||
}
|
}
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
|
||||||
if err != nil {
|
|
||||||
fatalTestError("unable to read the new repo root: %v\n", err)
|
|
||||||
}
|
|
||||||
for _, ownerDir := range ownerDirs {
|
|
||||||
if !ownerDir.Type().IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
|
|
||||||
if err != nil {
|
|
||||||
fatalTestError("unable to read the new repo root: %v\n", err)
|
|
||||||
}
|
|
||||||
for _, repoDir := range repoDirs {
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(testOpts) > 0 && testOpts[0].SetUp != nil {
|
if len(testOpts) > 0 && testOpts[0].SetUp != nil {
|
||||||
if err := testOpts[0].SetUp(); err != nil {
|
if err := testOpts[0].SetUp(); err != nil {
|
||||||
@ -255,24 +233,7 @@ func PrepareTestDatabase() error {
|
|||||||
// by tests that use the above MainTest(..) function.
|
// by tests that use the above MainTest(..) function.
|
||||||
func PrepareTestEnv(t testing.TB) {
|
func PrepareTestEnv(t testing.TB) {
|
||||||
assert.NoError(t, PrepareTestDatabase())
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
|
|
||||||
metaPath := filepath.Join(giteaRoot, "tests", "gitea-repositories-meta")
|
metaPath := filepath.Join(giteaRoot, "tests", "gitea-repositories-meta")
|
||||||
assert.NoError(t, CopyDir(metaPath, setting.RepoRootPath))
|
assert.NoError(t, SyncDirs(metaPath, setting.RepoRootPath))
|
||||||
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, ownerDir := range ownerDirs {
|
|
||||||
if !ownerDir.Type().IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, repoDir := range repoDirs {
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755)
|
|
||||||
_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base.SetupGiteaRoot() // Makes sure GITEA_ROOT is set
|
base.SetupGiteaRoot() // Makes sure GITEA_ROOT is set
|
||||||
}
|
}
|
||||||
|
@ -48,19 +48,19 @@ const (
|
|||||||
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
|
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
|
||||||
|
|
||||||
// UserTypeOrganization defines an organization
|
// UserTypeOrganization defines an organization
|
||||||
UserTypeOrganization
|
UserTypeOrganization // 1
|
||||||
|
|
||||||
// UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
|
// UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on
|
||||||
UserTypeUserReserved
|
UserTypeUserReserved // 2
|
||||||
|
|
||||||
// UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
|
// UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved
|
||||||
UserTypeOrganizationReserved
|
UserTypeOrganizationReserved // 3
|
||||||
|
|
||||||
// UserTypeBot defines a bot user
|
// UserTypeBot defines a bot user
|
||||||
UserTypeBot
|
UserTypeBot // 4
|
||||||
|
|
||||||
// UserTypeRemoteUser defines a remote user for federated users
|
// UserTypeRemoteUser defines a remote user for federated users
|
||||||
UserTypeRemoteUser
|
UserTypeRemoteUser // 5
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -884,7 +884,13 @@ func UpdateUserCols(ctx context.Context, u *User, cols ...string) error {
|
|||||||
|
|
||||||
// GetInactiveUsers gets all inactive users
|
// GetInactiveUsers gets all inactive users
|
||||||
func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
|
func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
|
||||||
var cond builder.Cond = builder.Eq{"is_active": false}
|
cond := builder.And(
|
||||||
|
builder.Eq{"is_active": false},
|
||||||
|
builder.Or( // only plain user
|
||||||
|
builder.Eq{"`type`": UserTypeIndividual},
|
||||||
|
builder.Eq{"`type`": UserTypeUserReserved},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if olderThan > 0 {
|
if olderThan > 0 {
|
||||||
cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()})
|
cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()})
|
||||||
|
@ -588,3 +588,17 @@ func TestDisabledUserFeatures(t *testing.T) {
|
|||||||
assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetInactiveUsers(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
// all inactive users
|
||||||
|
// user1's createdunix is 1730468968
|
||||||
|
users, err := user_model.GetInactiveUsers(db.DefaultContext, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, users, 1)
|
||||||
|
interval := time.Now().Unix() - 1730468968 + 3600*24
|
||||||
|
users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second)))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, users, 0)
|
||||||
|
}
|
||||||
|
@ -147,6 +147,9 @@ func StringsToInt64s(strs []string) ([]int64, error) {
|
|||||||
}
|
}
|
||||||
ints := make([]int64, 0, len(strs))
|
ints := make([]int64, 0, len(strs))
|
||||||
for _, s := range strs {
|
for _, s := range strs {
|
||||||
|
if s == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
n, err := strconv.ParseInt(s, 10, 64)
|
n, err := strconv.ParseInt(s, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -152,6 +152,7 @@ func TestStringsToInt64s(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testSuccess(nil, nil)
|
testSuccess(nil, nil)
|
||||||
testSuccess([]string{}, []int64{})
|
testSuccess([]string{}, []int64{})
|
||||||
|
testSuccess([]string{""}, []int64{})
|
||||||
testSuccess([]string{"-1234"}, []int64{-1234})
|
testSuccess([]string{"-1234"}, []int64{-1234})
|
||||||
testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256})
|
testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256})
|
||||||
|
|
||||||
|
@ -31,8 +31,8 @@ func (s Set[T]) AddMultiple(values ...T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains determines whether a set contains the specified elements.
|
// Contains determines whether a set contains all these elements.
|
||||||
// Returns true if the set contains the specified element; otherwise, false.
|
// Returns true if the set contains all these elements; otherwise, false.
|
||||||
func (s Set[T]) Contains(values ...T) bool {
|
func (s Set[T]) Contains(values ...T) bool {
|
||||||
ret := true
|
ret := true
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
|
@ -18,7 +18,9 @@ func TestSet(t *testing.T) {
|
|||||||
|
|
||||||
assert.True(t, s.Contains("key1"))
|
assert.True(t, s.Contains("key1"))
|
||||||
assert.True(t, s.Contains("key2"))
|
assert.True(t, s.Contains("key2"))
|
||||||
|
assert.True(t, s.Contains("key1", "key2"))
|
||||||
assert.False(t, s.Contains("key3"))
|
assert.False(t, s.Contains("key3"))
|
||||||
|
assert.False(t, s.Contains("key1", "key3"))
|
||||||
|
|
||||||
assert.True(t, s.Remove("key2"))
|
assert.True(t, s.Remove("key2"))
|
||||||
assert.False(t, s.Contains("key2"))
|
assert.False(t, s.Contains("key2"))
|
||||||
|
@ -146,9 +146,8 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadBatchLine reads the header line from cat-file --batch
|
// ReadBatchLine reads the header line from cat-file --batch
|
||||||
// We expect:
|
// We expect: <oid> SP <type> SP <size> LF
|
||||||
// <sha> SP <type> SP <size> LF
|
// then leaving the rest of the stream "<contents> LF" to be read
|
||||||
// sha is a hex encoded here
|
|
||||||
func ReadBatchLine(rd *bufio.Reader) (sha []byte, typ string, size int64, err error) {
|
func ReadBatchLine(rd *bufio.Reader) (sha []byte, typ string, size int64, err error) {
|
||||||
typ, err = rd.ReadString('\n')
|
typ, err = rd.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -29,7 +28,7 @@ type Commit struct {
|
|||||||
Signature *CommitSignature
|
Signature *CommitSignature
|
||||||
|
|
||||||
Parents []ObjectID // ID strings
|
Parents []ObjectID // ID strings
|
||||||
submoduleCache *ObjectCache
|
submoduleCache *ObjectCache[*SubModule]
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitSignature represents a git commit signature part.
|
// CommitSignature represents a git commit signature part.
|
||||||
@ -357,69 +356,6 @@ func (c *Commit) GetFileContent(filename string, limit int) (string, error) {
|
|||||||
return string(bytes), nil
|
return string(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSubModules get all the sub modules of current revision git tree
|
|
||||||
func (c *Commit) GetSubModules() (*ObjectCache, error) {
|
|
||||||
if c.submoduleCache != nil {
|
|
||||||
return c.submoduleCache, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
entry, err := c.GetTreeEntryByPath(".gitmodules")
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(ErrNotExist); ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rd, err := entry.Blob().DataAsync()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rd.Close()
|
|
||||||
scanner := bufio.NewScanner(rd)
|
|
||||||
c.submoduleCache = newObjectCache()
|
|
||||||
var ismodule bool
|
|
||||||
var path string
|
|
||||||
for scanner.Scan() {
|
|
||||||
if strings.HasPrefix(scanner.Text(), "[submodule") {
|
|
||||||
ismodule = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ismodule {
|
|
||||||
fields := strings.Split(scanner.Text(), "=")
|
|
||||||
k := strings.TrimSpace(fields[0])
|
|
||||||
if k == "path" {
|
|
||||||
path = strings.TrimSpace(fields[1])
|
|
||||||
} else if k == "url" {
|
|
||||||
c.submoduleCache.Set(path, &SubModule{path, strings.TrimSpace(fields[1])})
|
|
||||||
ismodule = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = scanner.Err(); err != nil {
|
|
||||||
return nil, fmt.Errorf("GetSubModules scan: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.submoduleCache, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSubModule get the sub module according entryname
|
|
||||||
func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
|
|
||||||
modules, err := c.GetSubModules()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if modules != nil {
|
|
||||||
module, has := modules.Get(entryname)
|
|
||||||
if has {
|
|
||||||
return module.(*SubModule), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
||||||
func (c *Commit) GetBranchName() (string, error) {
|
func (c *Commit) GetBranchName() (string, error) {
|
||||||
cmd := NewCommand(c.repo.Ctx, "name-rev")
|
cmd := NewCommand(c.repo.Ctx, "name-rev")
|
||||||
|
@ -7,5 +7,5 @@ package git
|
|||||||
type CommitInfo struct {
|
type CommitInfo struct {
|
||||||
Entry *TreeEntry
|
Entry *TreeEntry
|
||||||
Commit *Commit
|
Commit *Commit
|
||||||
SubModuleFile *SubModuleFile
|
SubModuleFile *CommitSubModuleFile
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
|||||||
commitsInfo[i].Commit = entryCommit
|
commitsInfo[i].Commit = entryCommit
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the entry if a submodule add a submodule file for this
|
// If the entry is a submodule add a submodule file for this
|
||||||
if entry.IsSubModule() {
|
if entry.IsSubModule() {
|
||||||
subModuleURL := ""
|
subModuleURL := ""
|
||||||
var fullPath string
|
var fullPath string
|
||||||
@ -85,7 +85,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
|||||||
} else if subModule != nil {
|
} else if subModule != nil {
|
||||||
subModuleURL = subModule.URL
|
subModuleURL = subModule.URL
|
||||||
}
|
}
|
||||||
subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String())
|
subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String())
|
||||||
commitsInfo[i].SubModuleFile = subModuleFile
|
commitsInfo[i].SubModuleFile = subModuleFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
|||||||
} else if subModule != nil {
|
} else if subModule != nil {
|
||||||
subModuleURL = subModule.URL
|
subModuleURL = subModule.URL
|
||||||
}
|
}
|
||||||
subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String())
|
subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String())
|
||||||
commitsInfo[i].SubModuleFile = subModuleFile
|
commitsInfo[i].SubModuleFile = subModuleFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
modules/git/commit_submodule.go
Normal file
47
modules/git/commit_submodule.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
// GetSubModules get all the submodules of current revision git tree
|
||||||
|
func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) {
|
||||||
|
if c.submoduleCache != nil {
|
||||||
|
return c.submoduleCache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := c.GetTreeEntryByPath(".gitmodules")
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(ErrNotExist); ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rd, err := entry.Blob().DataAsync()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rd.Close()
|
||||||
|
|
||||||
|
// at the moment we do not strictly limit the size of the .gitmodules file because some users would have huge .gitmodules files (>1MB)
|
||||||
|
c.submoduleCache, err = configParseSubModules(rd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.submoduleCache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubModule get the submodule according entry name
|
||||||
|
func (c *Commit) GetSubModule(entryName string) (*SubModule, error) {
|
||||||
|
modules, err := c.GetSubModules()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if modules != nil {
|
||||||
|
if module, has := modules.Get(entryName); has {
|
||||||
|
return module, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -15,24 +15,15 @@ import (
|
|||||||
|
|
||||||
var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
|
var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
|
||||||
|
|
||||||
// SubModule submodule is a reference on git repository
|
// CommitSubModuleFile represents a file with submodule type.
|
||||||
type SubModule struct {
|
type CommitSubModuleFile struct {
|
||||||
Name string
|
|
||||||
URL string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubModuleFile represents a file with submodule type.
|
|
||||||
type SubModuleFile struct {
|
|
||||||
*Commit
|
|
||||||
|
|
||||||
refURL string
|
refURL string
|
||||||
refID string
|
refID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSubModuleFile create a new submodule file
|
// NewCommitSubModuleFile create a new submodule file
|
||||||
func NewSubModuleFile(c *Commit, refURL, refID string) *SubModuleFile {
|
func NewCommitSubModuleFile(refURL, refID string) *CommitSubModuleFile {
|
||||||
return &SubModuleFile{
|
return &CommitSubModuleFile{
|
||||||
Commit: c,
|
|
||||||
refURL: refURL,
|
refURL: refURL,
|
||||||
refID: refID,
|
refID: refID,
|
||||||
}
|
}
|
||||||
@ -109,11 +100,12 @@ func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RefURL guesses and returns reference URL.
|
// RefURL guesses and returns reference URL.
|
||||||
func (sf *SubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string {
|
// FIXME: template passes AppURL as urlPrefix, it needs to figure out the correct approach (no hard-coded AppURL anymore)
|
||||||
|
func (sf *CommitSubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string {
|
||||||
return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain)
|
return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefID returns reference ID.
|
// RefID returns reference ID.
|
||||||
func (sf *SubModuleFile) RefID() string {
|
func (sf *CommitSubModuleFile) RefID() string {
|
||||||
return sf.refID
|
return sf.refID
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetRefURL(t *testing.T) {
|
func TestCommitSubModuleFileGetRefURL(t *testing.T) {
|
||||||
kases := []struct {
|
kases := []struct {
|
||||||
refURL string
|
refURL string
|
||||||
prefixURL string
|
prefixURL string
|
@ -135,7 +135,7 @@ author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
|||||||
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
|
||||||
encoding ISO-8859-1
|
encoding ISO-8859-1
|
||||||
gpgsig -----BEGIN PGP SIGNATURE-----
|
gpgsig -----BEGIN PGP SIGNATURE-----
|
||||||
|
<SPACE>
|
||||||
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
|
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
|
||||||
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
|
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
|
||||||
gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
|
gizKa2COiGtugv8fE+TKqXKaJx6uJUJEjaBd8E9Af9PrAzjWj+A84lU6/PgPS8hq
|
||||||
@ -150,7 +150,7 @@ gpgsig -----BEGIN PGP SIGNATURE-----
|
|||||||
-----END PGP SIGNATURE-----
|
-----END PGP SIGNATURE-----
|
||||||
|
|
||||||
ISO-8859-1`
|
ISO-8859-1`
|
||||||
|
commitString = strings.ReplaceAll(commitString, "<SPACE>", " ")
|
||||||
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}
|
||||||
gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
|
gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
187
modules/git/config.go
Normal file
187
modules/git/config.go
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem)
|
||||||
|
func syncGitConfig() (err error) {
|
||||||
|
if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// first, write user's git config options to git config file
|
||||||
|
// user config options could be overwritten by builtin values later, because if a value is builtin, it must have some special purposes
|
||||||
|
for k, v := range setting.GitConfig.Options {
|
||||||
|
if err = configSet(strings.ToLower(k), v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults"
|
||||||
|
// TODO: need to confirm whether users really need to change these values manually. It seems that these values are dummy only and not really used.
|
||||||
|
// If these values are not really used, then they can be set (overwritten) directly without considering about existence.
|
||||||
|
for configKey, defaultValue := range map[string]string{
|
||||||
|
"user.name": "Gitea",
|
||||||
|
"user.email": "gitea@fake.local",
|
||||||
|
} {
|
||||||
|
if err := configSetNonExist(configKey, defaultValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set git some configurations - these must be set to these values for gitea to work correctly
|
||||||
|
if err := configSet("core.quotePath", "false"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if DefaultFeatures().CheckVersionAtLeast("2.10") {
|
||||||
|
if err := configSet("receive.advertisePushOptions", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||||
|
if err := configSet("core.commitGraph", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := configSet("gc.writeCommitGraph", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := configSet("fetch.writeCommitGraph", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if DefaultFeatures().SupportProcReceive {
|
||||||
|
// set support for AGit flow
|
||||||
|
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user.
|
||||||
|
// However, some docker users and samba users find it difficult to configure their systems correctly,
|
||||||
|
// so that Gitea's git repositories are owned by the Gitea user.
|
||||||
|
// (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.)
|
||||||
|
// See issue: https://github.com/go-gitea/gitea/issues/19455
|
||||||
|
// As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea,
|
||||||
|
// it is now safe to set "safe.directory=*" for internal usage only.
|
||||||
|
// Although this setting is only supported by some new git versions, it is also tolerated by earlier versions
|
||||||
|
if err := configAddNonExist("safe.directory", "*"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
if err := configSet("core.longpaths", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if setting.Git.DisableCoreProtectNTFS {
|
||||||
|
err = configSet("core.protectNTFS", "false")
|
||||||
|
} else {
|
||||||
|
err = configUnsetAll("core.protectNTFS", "false")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default partial clones are disabled, enable them from git v2.22
|
||||||
|
if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") {
|
||||||
|
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = configSet("uploadpack.allowAnySHA1InWant", "true")
|
||||||
|
} else {
|
||||||
|
if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func configSet(key, value string) error {
|
||||||
|
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||||
|
if err != nil && !IsErrorExitCode(err, 1) {
|
||||||
|
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currValue := strings.TrimSpace(stdout)
|
||||||
|
if currValue == value {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func configSetNonExist(key, value string) error {
|
||||||
|
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||||
|
if err == nil {
|
||||||
|
// already exist
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if IsErrorExitCode(err, 1) {
|
||||||
|
// not exist, set new config
|
||||||
|
_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func configAddNonExist(key, value string) error {
|
||||||
|
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
||||||
|
if err == nil {
|
||||||
|
// already exist
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if IsErrorExitCode(err, 1) {
|
||||||
|
// not exist, add new config
|
||||||
|
_, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add git global config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func configUnsetAll(key, value string) error {
|
||||||
|
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
||||||
|
if err == nil {
|
||||||
|
// exist, need to remove
|
||||||
|
_, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if IsErrorExitCode(err, 1) {
|
||||||
|
// not exist
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
||||||
|
}
|
75
modules/git/config_submodule.go
Normal file
75
modules/git/config_submodule.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubModule is a reference on git repository
|
||||||
|
type SubModule struct {
|
||||||
|
Path string
|
||||||
|
URL string
|
||||||
|
Branch string // this field is newly added but not really used
|
||||||
|
}
|
||||||
|
|
||||||
|
// configParseSubModules this is not a complete parse for gitmodules file, it only
|
||||||
|
// parses the url and path of submodules. At the moment it only parses well-formed gitmodules files.
|
||||||
|
// In the future, there should be a complete implementation of https://git-scm.com/docs/git-config#_syntax
|
||||||
|
func configParseSubModules(r io.Reader) (*ObjectCache[*SubModule], error) {
|
||||||
|
var subModule *SubModule
|
||||||
|
subModules := newObjectCache[*SubModule]()
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
// Skip empty lines and comments
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section header [section]
|
||||||
|
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||||
|
if subModule != nil {
|
||||||
|
subModules.Set(subModule.Path, subModule)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "[submodule") {
|
||||||
|
subModule = &SubModule{}
|
||||||
|
} else {
|
||||||
|
subModule = nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if subModule == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := strings.TrimSpace(parts[0])
|
||||||
|
value := strings.TrimSpace(parts[1])
|
||||||
|
switch key {
|
||||||
|
case "path":
|
||||||
|
subModule.Path = value
|
||||||
|
case "url":
|
||||||
|
subModule.URL = value
|
||||||
|
case "branch":
|
||||||
|
subModule.Branch = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading file: %w", err)
|
||||||
|
}
|
||||||
|
if subModule != nil {
|
||||||
|
subModules.Set(subModule.Path, subModule)
|
||||||
|
}
|
||||||
|
return subModules, nil
|
||||||
|
}
|
49
modules/git/config_submodule_test.go
Normal file
49
modules/git/config_submodule_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigSubmodule(t *testing.T) {
|
||||||
|
input := `
|
||||||
|
[core]
|
||||||
|
path = test
|
||||||
|
|
||||||
|
[submodule "submodule1"]
|
||||||
|
path = path1
|
||||||
|
url = https://gitea.io/foo/foo
|
||||||
|
#branch = b1
|
||||||
|
|
||||||
|
[other1]
|
||||||
|
branch = master
|
||||||
|
|
||||||
|
[submodule "submodule2"]
|
||||||
|
path = path2
|
||||||
|
url = https://gitea.io/bar/bar
|
||||||
|
branch = b2
|
||||||
|
|
||||||
|
[other2]
|
||||||
|
branch = main
|
||||||
|
|
||||||
|
[submodule "submodule3"]
|
||||||
|
path = path3
|
||||||
|
url = https://gitea.io/xxx/xxx
|
||||||
|
`
|
||||||
|
|
||||||
|
subModules, err := configParseSubModules(strings.NewReader(input))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, subModules.cache, 3)
|
||||||
|
|
||||||
|
sm1, _ := subModules.Get("path1")
|
||||||
|
assert.Equal(t, &SubModule{Path: "path1", URL: "https://gitea.io/foo/foo", Branch: ""}, sm1)
|
||||||
|
sm2, _ := subModules.Get("path2")
|
||||||
|
assert.Equal(t, &SubModule{Path: "path2", URL: "https://gitea.io/bar/bar", Branch: "b2"}, sm2)
|
||||||
|
sm3, _ := subModules.Get("path3")
|
||||||
|
assert.Equal(t, &SubModule{Path: "path3", URL: "https://gitea.io/xxx/xxx", Branch: ""}, sm3)
|
||||||
|
}
|
66
modules/git/config_test.go
Normal file
66
modules/git/config_test.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func gitConfigContains(sub string) bool {
|
||||||
|
if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil {
|
||||||
|
return strings.Contains(string(b), sub)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitConfig(t *testing.T) {
|
||||||
|
assert.False(t, gitConfigContains("key-a"))
|
||||||
|
|
||||||
|
assert.NoError(t, configSetNonExist("test.key-a", "val-a"))
|
||||||
|
assert.True(t, gitConfigContains("key-a = val-a"))
|
||||||
|
|
||||||
|
assert.NoError(t, configSetNonExist("test.key-a", "val-a-changed"))
|
||||||
|
assert.False(t, gitConfigContains("key-a = val-a-changed"))
|
||||||
|
|
||||||
|
assert.NoError(t, configSet("test.key-a", "val-a-changed"))
|
||||||
|
assert.True(t, gitConfigContains("key-a = val-a-changed"))
|
||||||
|
|
||||||
|
assert.NoError(t, configAddNonExist("test.key-b", "val-b"))
|
||||||
|
assert.True(t, gitConfigContains("key-b = val-b"))
|
||||||
|
|
||||||
|
assert.NoError(t, configAddNonExist("test.key-b", "val-2b"))
|
||||||
|
assert.True(t, gitConfigContains("key-b = val-b"))
|
||||||
|
assert.True(t, gitConfigContains("key-b = val-2b"))
|
||||||
|
|
||||||
|
assert.NoError(t, configUnsetAll("test.key-b", "val-b"))
|
||||||
|
assert.False(t, gitConfigContains("key-b = val-b"))
|
||||||
|
assert.True(t, gitConfigContains("key-b = val-2b"))
|
||||||
|
|
||||||
|
assert.NoError(t, configUnsetAll("test.key-b", "val-2b"))
|
||||||
|
assert.False(t, gitConfigContains("key-b = val-2b"))
|
||||||
|
|
||||||
|
assert.NoError(t, configSet("test.key-x", "*"))
|
||||||
|
assert.True(t, gitConfigContains("key-x = *"))
|
||||||
|
assert.NoError(t, configSetNonExist("test.key-x", "*"))
|
||||||
|
assert.NoError(t, configUnsetAll("test.key-x", "*"))
|
||||||
|
assert.False(t, gitConfigContains("key-x = *"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncConfig(t *testing.T) {
|
||||||
|
oldGitConfig := setting.GitConfig
|
||||||
|
defer func() {
|
||||||
|
setting.GitConfig = oldGitConfig
|
||||||
|
}()
|
||||||
|
|
||||||
|
setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA"
|
||||||
|
assert.NoError(t, syncGitConfig())
|
||||||
|
assert.True(t, gitConfigContains("[sync-test]"))
|
||||||
|
assert.True(t, gitConfigContains("cfg-key-a = CfgValA"))
|
||||||
|
}
|
14
modules/git/fsck.go
Normal file
14
modules/git/fsck.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fsck verifies the connectivity and validity of the objects in the database
|
||||||
|
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error {
|
||||||
|
return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath})
|
||||||
|
}
|
@ -11,7 +11,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -95,17 +94,18 @@ func parseGitVersionLine(s string) (*version.Version, error) {
|
|||||||
return version.NewVersion(versionString)
|
return version.NewVersion(versionString)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetExecutablePath changes the path of git executable and checks the file permission and version.
|
func checkGitVersionCompatibility(gitVer *version.Version) error {
|
||||||
func SetExecutablePath(path string) error {
|
badVersions := []struct {
|
||||||
// If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
|
Version *version.Version
|
||||||
if path != "" {
|
Reason string
|
||||||
GitExecutable = path
|
}{
|
||||||
|
{version.Must(version.NewVersion("2.43.1")), "regression bug of GIT_FLUSH"},
|
||||||
|
}
|
||||||
|
for _, bad := range badVersions {
|
||||||
|
if gitVer.Equal(bad.Version) {
|
||||||
|
return errors.New(bad.Reason)
|
||||||
}
|
}
|
||||||
absPath, err := exec.LookPath(GitExecutable)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("git not found: %w", err)
|
|
||||||
}
|
}
|
||||||
GitExecutable = absPath
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +128,20 @@ func ensureGitVersion() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetExecutablePath changes the path of git executable and checks the file permission and version.
|
||||||
|
func SetExecutablePath(path string) error {
|
||||||
|
// If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
|
||||||
|
if path != "" {
|
||||||
|
GitExecutable = path
|
||||||
|
}
|
||||||
|
absPath, err := exec.LookPath(GitExecutable)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("git not found: %w", err)
|
||||||
|
}
|
||||||
|
GitExecutable = absPath
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// HomeDir is the home dir for git to store the global config file used by Gitea internally
|
// HomeDir is the home dir for git to store the global config file used by Gitea internally
|
||||||
func HomeDir() string {
|
func HomeDir() string {
|
||||||
if setting.Git.HomePath == "" {
|
if setting.Git.HomePath == "" {
|
||||||
@ -204,196 +218,3 @@ func InitFull(ctx context.Context) (err error) {
|
|||||||
|
|
||||||
return syncGitConfig()
|
return syncGitConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem)
|
|
||||||
func syncGitConfig() (err error) {
|
|
||||||
if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil {
|
|
||||||
return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// first, write user's git config options to git config file
|
|
||||||
// user config options could be overwritten by builtin values later, because if a value is builtin, it must have some special purposes
|
|
||||||
for k, v := range setting.GitConfig.Options {
|
|
||||||
if err = configSet(strings.ToLower(k), v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults"
|
|
||||||
// TODO: need to confirm whether users really need to change these values manually. It seems that these values are dummy only and not really used.
|
|
||||||
// If these values are not really used, then they can be set (overwritten) directly without considering about existence.
|
|
||||||
for configKey, defaultValue := range map[string]string{
|
|
||||||
"user.name": "Gitea",
|
|
||||||
"user.email": "gitea@fake.local",
|
|
||||||
} {
|
|
||||||
if err := configSetNonExist(configKey, defaultValue); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set git some configurations - these must be set to these values for gitea to work correctly
|
|
||||||
if err := configSet("core.quotePath", "false"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if DefaultFeatures().CheckVersionAtLeast("2.10") {
|
|
||||||
if err := configSet("receive.advertisePushOptions", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
|
||||||
if err := configSet("core.commitGraph", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := configSet("gc.writeCommitGraph", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := configSet("fetch.writeCommitGraph", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if DefaultFeatures().SupportProcReceive {
|
|
||||||
// set support for AGit flow
|
|
||||||
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user.
|
|
||||||
// However, some docker users and samba users find it difficult to configure their systems correctly,
|
|
||||||
// so that Gitea's git repositories are owned by the Gitea user.
|
|
||||||
// (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.)
|
|
||||||
// See issue: https://github.com/go-gitea/gitea/issues/19455
|
|
||||||
// As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea,
|
|
||||||
// it is now safe to set "safe.directory=*" for internal usage only.
|
|
||||||
// Although this setting is only supported by some new git versions, it is also tolerated by earlier versions
|
|
||||||
if err := configAddNonExist("safe.directory", "*"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
if err := configSet("core.longpaths", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if setting.Git.DisableCoreProtectNTFS {
|
|
||||||
err = configSet("core.protectNTFS", "false")
|
|
||||||
} else {
|
|
||||||
err = configUnsetAll("core.protectNTFS", "false")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default partial clones are disabled, enable them from git v2.22
|
|
||||||
if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") {
|
|
||||||
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = configSet("uploadpack.allowAnySHA1InWant", "true")
|
|
||||||
} else {
|
|
||||||
if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkGitVersionCompatibility(gitVer *version.Version) error {
|
|
||||||
badVersions := []struct {
|
|
||||||
Version *version.Version
|
|
||||||
Reason string
|
|
||||||
}{
|
|
||||||
{version.Must(version.NewVersion("2.43.1")), "regression bug of GIT_FLUSH"},
|
|
||||||
}
|
|
||||||
for _, bad := range badVersions {
|
|
||||||
if gitVer.Equal(bad.Version) {
|
|
||||||
return errors.New(bad.Reason)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func configSet(key, value string) error {
|
|
||||||
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
|
||||||
if err != nil && !IsErrorExitCode(err, 1) {
|
|
||||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
currValue := strings.TrimSpace(stdout)
|
|
||||||
if currValue == value {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func configSetNonExist(key, value string) error {
|
|
||||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
|
||||||
if err == nil {
|
|
||||||
// already exist
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if IsErrorExitCode(err, 1) {
|
|
||||||
// not exist, set new config
|
|
||||||
_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func configAddNonExist(key, value string) error {
|
|
||||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
|
||||||
if err == nil {
|
|
||||||
// already exist
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if IsErrorExitCode(err, 1) {
|
|
||||||
// not exist, add new config
|
|
||||||
_, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to add git global config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func configUnsetAll(key, value string) error {
|
|
||||||
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
|
|
||||||
if err == nil {
|
|
||||||
// exist, need to remove
|
|
||||||
_, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if IsErrorExitCode(err, 1) {
|
|
||||||
// not exist
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fsck verifies the connectivity and validity of the objects in the database
|
|
||||||
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error {
|
|
||||||
return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath})
|
|
||||||
}
|
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -43,58 +42,6 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gitConfigContains(sub string) bool {
|
|
||||||
if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil {
|
|
||||||
return strings.Contains(string(b), sub)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGitConfig(t *testing.T) {
|
|
||||||
assert.False(t, gitConfigContains("key-a"))
|
|
||||||
|
|
||||||
assert.NoError(t, configSetNonExist("test.key-a", "val-a"))
|
|
||||||
assert.True(t, gitConfigContains("key-a = val-a"))
|
|
||||||
|
|
||||||
assert.NoError(t, configSetNonExist("test.key-a", "val-a-changed"))
|
|
||||||
assert.False(t, gitConfigContains("key-a = val-a-changed"))
|
|
||||||
|
|
||||||
assert.NoError(t, configSet("test.key-a", "val-a-changed"))
|
|
||||||
assert.True(t, gitConfigContains("key-a = val-a-changed"))
|
|
||||||
|
|
||||||
assert.NoError(t, configAddNonExist("test.key-b", "val-b"))
|
|
||||||
assert.True(t, gitConfigContains("key-b = val-b"))
|
|
||||||
|
|
||||||
assert.NoError(t, configAddNonExist("test.key-b", "val-2b"))
|
|
||||||
assert.True(t, gitConfigContains("key-b = val-b"))
|
|
||||||
assert.True(t, gitConfigContains("key-b = val-2b"))
|
|
||||||
|
|
||||||
assert.NoError(t, configUnsetAll("test.key-b", "val-b"))
|
|
||||||
assert.False(t, gitConfigContains("key-b = val-b"))
|
|
||||||
assert.True(t, gitConfigContains("key-b = val-2b"))
|
|
||||||
|
|
||||||
assert.NoError(t, configUnsetAll("test.key-b", "val-2b"))
|
|
||||||
assert.False(t, gitConfigContains("key-b = val-2b"))
|
|
||||||
|
|
||||||
assert.NoError(t, configSet("test.key-x", "*"))
|
|
||||||
assert.True(t, gitConfigContains("key-x = *"))
|
|
||||||
assert.NoError(t, configSetNonExist("test.key-x", "*"))
|
|
||||||
assert.NoError(t, configUnsetAll("test.key-x", "*"))
|
|
||||||
assert.False(t, gitConfigContains("key-x = *"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSyncConfig(t *testing.T) {
|
|
||||||
oldGitConfig := setting.GitConfig
|
|
||||||
defer func() {
|
|
||||||
setting.GitConfig = oldGitConfig
|
|
||||||
}()
|
|
||||||
|
|
||||||
setting.GitConfig.Options["sync-test.cfg-key-a"] = "CfgValA"
|
|
||||||
assert.NoError(t, syncGitConfig())
|
|
||||||
assert.True(t, gitConfigContains("[sync-test]"))
|
|
||||||
assert.True(t, gitConfigContains("cfg-key-a = CfgValA"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseGitVersion(t *testing.T) {
|
func TestParseGitVersion(t *testing.T) {
|
||||||
v, err := parseGitVersionLine("git version 2.29.3")
|
v, err := parseGitVersionLine("git version 2.29.3")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -28,7 +28,7 @@ const isGogit = true
|
|||||||
type Repository struct {
|
type Repository struct {
|
||||||
Path string
|
Path string
|
||||||
|
|
||||||
tagCache *ObjectCache
|
tagCache *ObjectCache[*Tag]
|
||||||
|
|
||||||
gogitRepo *gogit.Repository
|
gogitRepo *gogit.Repository
|
||||||
gogitStorage *filesystem.Storage
|
gogitStorage *filesystem.Storage
|
||||||
@ -79,7 +79,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
|
|||||||
Path: repoPath,
|
Path: repoPath,
|
||||||
gogitRepo: gogitRepo,
|
gogitRepo: gogitRepo,
|
||||||
gogitStorage: storage,
|
gogitStorage: storage,
|
||||||
tagCache: newObjectCache(),
|
tagCache: newObjectCache[*Tag](),
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
objectFormat: ParseGogitHash(plumbing.ZeroHash).Type(),
|
objectFormat: ParseGogitHash(plumbing.ZeroHash).Type(),
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -21,7 +21,7 @@ const isGogit = false
|
|||||||
type Repository struct {
|
type Repository struct {
|
||||||
Path string
|
Path string
|
||||||
|
|
||||||
tagCache *ObjectCache
|
tagCache *ObjectCache[*Tag]
|
||||||
|
|
||||||
gpgSettings *GPGSettings
|
gpgSettings *GPGSettings
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
|
|||||||
|
|
||||||
return &Repository{
|
return &Repository{
|
||||||
Path: repoPath,
|
Path: repoPath,
|
||||||
tagCache: newObjectCache(),
|
tagCache: newObjectCache[*Tag](),
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
|
|||||||
t, ok := repo.tagCache.Get(tagID.String())
|
t, ok := repo.tagCache.Get(tagID.String())
|
||||||
if ok {
|
if ok {
|
||||||
log.Debug("Hit cache: %s", tagID)
|
log.Debug("Hit cache: %s", tagID)
|
||||||
tagClone := *t.(*Tag)
|
tagClone := *t
|
||||||
tagClone.Name = name // This is necessary because lightweight tags may have same id
|
tagClone.Name = name // This is necessary because lightweight tags may have same id
|
||||||
return &tagClone, nil
|
return &tagClone, nil
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
|
|||||||
t, ok := repo.tagCache.Get(tagID.String())
|
t, ok := repo.tagCache.Get(tagID.String())
|
||||||
if ok {
|
if ok {
|
||||||
log.Debug("Hit cache: %s", tagID)
|
log.Debug("Hit cache: %s", tagID)
|
||||||
tagClone := *t.(*Tag)
|
tagClone := *t
|
||||||
tagClone.Name = name // This is necessary because lightweight tags may have same id
|
tagClone.Name = name // This is necessary because lightweight tags may have same id
|
||||||
return &tagClone, nil
|
return &tagClone, nil
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -1,6 +0,0 @@
|
|||||||
# git ls-files --others --exclude-from=.git/info/exclude
|
|
||||||
# Lines that start with '#' are comments.
|
|
||||||
# For a project mostly in C, the following would be a good set of
|
|
||||||
# exclude patterns (uncomment them if you want to use them):
|
|
||||||
# *.[oa]
|
|
||||||
# *~
|
|
@ -1 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -1,6 +0,0 @@
|
|||||||
# git ls-files --others --exclude-from=.git/info/exclude
|
|
||||||
# Lines that start with '#' are comments.
|
|
||||||
# For a project mostly in C, the following would be a good set of
|
|
||||||
# exclude patterns (uncomment them if you want to use them):
|
|
||||||
# *.[oa]
|
|
||||||
# *~
|
|
@ -1 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -1,6 +0,0 @@
|
|||||||
# git ls-files --others --exclude-from=.git/info/exclude
|
|
||||||
# Lines that start with '#' are comments.
|
|
||||||
# For a project mostly in C, the following would be a good set of
|
|
||||||
# exclude patterns (uncomment them if you want to use them):
|
|
||||||
# *.[oa]
|
|
||||||
# *~
|
|
@ -1 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -1,6 +0,0 @@
|
|||||||
# git ls-files --others --exclude-from=.git/info/exclude
|
|
||||||
# Lines that start with '#' are comments.
|
|
||||||
# For a project mostly in C, the following would be a good set of
|
|
||||||
# exclude patterns (uncomment them if you want to use them):
|
|
||||||
# *.[oa]
|
|
||||||
# *~
|
|
@ -1 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -1 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -1,6 +0,0 @@
|
|||||||
# git ls-files --others --exclude-from=.git/info/exclude
|
|
||||||
# Lines that start with '#' are comments.
|
|
||||||
# For a project mostly in C, the following would be a good set of
|
|
||||||
# exclude patterns (uncomment them if you want to use them):
|
|
||||||
# *.[oa]
|
|
||||||
# *~
|
|
@ -1 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -1 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -1,6 +0,0 @@
|
|||||||
# git ls-files --others --exclude-from=.git/info/exclude
|
|
||||||
# Lines that start with '#' are comments.
|
|
||||||
# For a project mostly in C, the following would be a good set of
|
|
||||||
# exclude patterns (uncomment them if you want to use them):
|
|
||||||
# *.[oa]
|
|
||||||
# *~
|
|
@ -1 +0,0 @@
|
|||||||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -1,6 +0,0 @@
|
|||||||
# git ls-files --others --exclude-from=.git/info/exclude
|
|
||||||
# Lines that start with '#' are comments.
|
|
||||||
# For a project mostly in C, the following would be a good set of
|
|
||||||
# exclude patterns (uncomment them if you want to use them):
|
|
||||||
# *.[oa]
|
|
||||||
# *~
|
|
@ -15,27 +15,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ObjectCache provides thread-safe cache operations.
|
// ObjectCache provides thread-safe cache operations.
|
||||||
type ObjectCache struct {
|
type ObjectCache[T any] struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
cache map[string]any
|
cache map[string]T
|
||||||
}
|
}
|
||||||
|
|
||||||
func newObjectCache() *ObjectCache {
|
func newObjectCache[T any]() *ObjectCache[T] {
|
||||||
return &ObjectCache{
|
return &ObjectCache[T]{cache: make(map[string]T, 10)}
|
||||||
cache: make(map[string]any, 10),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set add obj to cache
|
// Set adds obj to cache
|
||||||
func (oc *ObjectCache) Set(id string, obj any) {
|
func (oc *ObjectCache[T]) Set(id string, obj T) {
|
||||||
oc.lock.Lock()
|
oc.lock.Lock()
|
||||||
defer oc.lock.Unlock()
|
defer oc.lock.Unlock()
|
||||||
|
|
||||||
oc.cache[id] = obj
|
oc.cache[id] = obj
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get get cached obj by id
|
// Get gets cached obj by id
|
||||||
func (oc *ObjectCache) Get(id string) (any, bool) {
|
func (oc *ObjectCache[T]) Get(id string) (T, bool) {
|
||||||
oc.lock.RLock()
|
oc.lock.RLock()
|
||||||
defer oc.lock.RUnlock()
|
defer oc.lock.RUnlock()
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package html
|
|
||||||
|
|
||||||
// ParseSizeAndClass get size and class from string with default values
|
|
||||||
// If present, "others" expects the new size first and then the classes to use
|
|
||||||
func ParseSizeAndClass(defaultSize int, defaultClass string, others ...any) (int, string) {
|
|
||||||
size := defaultSize
|
|
||||||
if len(others) >= 1 {
|
|
||||||
if v, ok := others[0].(int); ok && v != 0 {
|
|
||||||
size = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class := defaultClass
|
|
||||||
if len(others) >= 2 {
|
|
||||||
if v, ok := others[1].(string); ok && v != "" {
|
|
||||||
if class != "" {
|
|
||||||
class += " "
|
|
||||||
}
|
|
||||||
class += v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return size, class
|
|
||||||
}
|
|
48
modules/htmlutil/html.go
Normal file
48
modules/htmlutil/html.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package htmlutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseSizeAndClass get size and class from string with default values
|
||||||
|
// If present, "others" expects the new size first and then the classes to use
|
||||||
|
func ParseSizeAndClass(defaultSize int, defaultClass string, others ...any) (int, string) {
|
||||||
|
size := defaultSize
|
||||||
|
if len(others) >= 1 {
|
||||||
|
if v, ok := others[0].(int); ok && v != 0 {
|
||||||
|
size = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class := defaultClass
|
||||||
|
if len(others) >= 2 {
|
||||||
|
if v, ok := others[1].(string); ok && v != "" {
|
||||||
|
if class != "" {
|
||||||
|
class += " "
|
||||||
|
}
|
||||||
|
class += v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size, class
|
||||||
|
}
|
||||||
|
|
||||||
|
func HTMLFormat(s string, rawArgs ...any) template.HTML {
|
||||||
|
args := slices.Clone(rawArgs)
|
||||||
|
for i, v := range args {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
|
||||||
|
// for most basic types (including template.HTML which is safe), just do nothing and use it
|
||||||
|
case string:
|
||||||
|
args[i] = template.HTMLEscapeString(v)
|
||||||
|
case fmt.Stringer:
|
||||||
|
args[i] = template.HTMLEscapeString(v.String())
|
||||||
|
default:
|
||||||
|
args[i] = template.HTMLEscapeString(fmt.Sprint(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return template.HTML(fmt.Sprintf(s, args...))
|
||||||
|
}
|
15
modules/htmlutil/html_test.go
Normal file
15
modules/htmlutil/html_test.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package htmlutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHTMLFormat(t *testing.T) {
|
||||||
|
assert.Equal(t, template.HTML("<a>< < 1</a>"), HTMLFormat("<a>%s %s %d</a>", "<", template.HTML("<"), 1))
|
||||||
|
}
|
@ -46,7 +46,7 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
|
|||||||
w.Header().Add(gzhttp.HeaderNoCompression, "1")
|
w.Header().Add(gzhttp.HeaderNoCompression, "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
contentType := typesniffer.ApplicationOctetStream
|
contentType := typesniffer.MimeTypeApplicationOctetStream
|
||||||
if opts.ContentType != "" {
|
if opts.ContentType != "" {
|
||||||
if opts.ContentTypeCharset != "" {
|
if opts.ContentTypeCharset != "" {
|
||||||
contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset)
|
contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset)
|
||||||
@ -107,7 +107,7 @@ func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, filePath stri
|
|||||||
} else if isPlain {
|
} else if isPlain {
|
||||||
opts.ContentType = "text/plain"
|
opts.ContentType = "text/plain"
|
||||||
} else {
|
} else {
|
||||||
opts.ContentType = typesniffer.ApplicationOctetStream
|
opts.ContentType = typesniffer.MimeTypeApplicationOctetStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
_ "code.gitea.io/gitea/models/activities"
|
_ "code.gitea.io/gitea/models/activities"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
@ -284,15 +285,11 @@ func TestBleveIndexAndSearch(t *testing.T) {
|
|||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
idx := bleve.NewIndexer(dir)
|
idx := bleve.NewIndexer(dir)
|
||||||
_, err := idx.Init(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
if idx != nil {
|
|
||||||
idx.Close()
|
|
||||||
}
|
|
||||||
assert.FailNow(t, "Unable to create bleve indexer Error: %v", err)
|
|
||||||
}
|
|
||||||
defer idx.Close()
|
defer idx.Close()
|
||||||
|
|
||||||
|
_, err := idx.Init(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
testIndexer("beleve", t, idx)
|
testIndexer("beleve", t, idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ type GiteaBackend struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
server *url.URL
|
server *url.URL
|
||||||
op string
|
op string
|
||||||
token string
|
authToken string
|
||||||
itoken string
|
internalAuth string
|
||||||
logger transfer.Logger
|
logger transfer.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ func New(ctx context.Context, repo, op, token string, logger transfer.Logger) (t
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
server = server.JoinPath("api/internal/repo", repo, "info/lfs")
|
server = server.JoinPath("api/internal/repo", repo, "info/lfs")
|
||||||
return &GiteaBackend{ctx: ctx, server: server, op: op, token: token, itoken: fmt.Sprintf("Bearer %s", setting.InternalToken), logger: logger}, nil
|
return &GiteaBackend{ctx: ctx, server: server, op: op, authToken: token, internalAuth: fmt.Sprintf("Bearer %s", setting.InternalToken), logger: logger}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch implements transfer.Backend
|
// Batch implements transfer.Backend
|
||||||
@ -73,8 +73,8 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
|
|||||||
}
|
}
|
||||||
url := g.server.JoinPath("objects/batch").String()
|
url := g.server.JoinPath("objects/batch").String()
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorisation: g.itoken,
|
headerAuthorization: g.authToken,
|
||||||
headerAuthX: g.token,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
|
|||||||
}
|
}
|
||||||
idMapStr := base64.StdEncoding.EncodeToString(idMapBytes)
|
idMapStr := base64.StdEncoding.EncodeToString(idMapBytes)
|
||||||
item.Args[argID] = idMapStr
|
item.Args[argID] = idMapStr
|
||||||
if authHeader, ok := action.Header[headerAuthorisation]; ok {
|
if authHeader, ok := action.Header[headerAuthorization]; ok {
|
||||||
authHeaderB64 := base64.StdEncoding.EncodeToString([]byte(authHeader))
|
authHeaderB64 := base64.StdEncoding.EncodeToString([]byte(authHeader))
|
||||||
item.Args[argToken] = authHeaderB64
|
item.Args[argToken] = authHeaderB64
|
||||||
}
|
}
|
||||||
@ -142,7 +142,7 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
|
|||||||
}
|
}
|
||||||
idMapStr := base64.StdEncoding.EncodeToString(idMapBytes)
|
idMapStr := base64.StdEncoding.EncodeToString(idMapBytes)
|
||||||
item.Args[argID] = idMapStr
|
item.Args[argID] = idMapStr
|
||||||
if authHeader, ok := action.Header[headerAuthorisation]; ok {
|
if authHeader, ok := action.Header[headerAuthorization]; ok {
|
||||||
authHeaderB64 := base64.StdEncoding.EncodeToString([]byte(authHeader))
|
authHeaderB64 := base64.StdEncoding.EncodeToString([]byte(authHeader))
|
||||||
item.Args[argToken] = authHeaderB64
|
item.Args[argToken] = authHeaderB64
|
||||||
}
|
}
|
||||||
@ -183,8 +183,8 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
|
|||||||
}
|
}
|
||||||
url := action.Href
|
url := action.Href
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorisation: g.itoken,
|
headerAuthorization: g.authToken,
|
||||||
headerAuthX: g.token,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeOctetStream,
|
headerAccept: mimeOctetStream,
|
||||||
}
|
}
|
||||||
req := newInternalRequest(g.ctx, url, http.MethodGet, headers, nil)
|
req := newInternalRequest(g.ctx, url, http.MethodGet, headers, nil)
|
||||||
@ -229,8 +229,8 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
|
|||||||
}
|
}
|
||||||
url := action.Href
|
url := action.Href
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorisation: g.itoken,
|
headerAuthorization: g.authToken,
|
||||||
headerAuthX: g.token,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerContentType: mimeOctetStream,
|
headerContentType: mimeOctetStream,
|
||||||
headerContentLength: strconv.FormatInt(size, 10),
|
headerContentLength: strconv.FormatInt(size, 10),
|
||||||
}
|
}
|
||||||
@ -279,8 +279,8 @@ func (g *GiteaBackend) Verify(oid string, size int64, args transfer.Args) (trans
|
|||||||
}
|
}
|
||||||
url := action.Href
|
url := action.Href
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorisation: g.itoken,
|
headerAuthorization: g.authToken,
|
||||||
headerAuthX: g.token,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
|
@ -24,14 +24,14 @@ type giteaLockBackend struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
g *GiteaBackend
|
g *GiteaBackend
|
||||||
server *url.URL
|
server *url.URL
|
||||||
token string
|
authToken string
|
||||||
itoken string
|
internalAuth string
|
||||||
logger transfer.Logger
|
logger transfer.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGiteaLockBackend(g *GiteaBackend) transfer.LockBackend {
|
func newGiteaLockBackend(g *GiteaBackend) transfer.LockBackend {
|
||||||
server := g.server.JoinPath("locks")
|
server := g.server.JoinPath("locks")
|
||||||
return &giteaLockBackend{ctx: g.ctx, g: g, server: server, token: g.token, itoken: g.itoken, logger: g.logger}
|
return &giteaLockBackend{ctx: g.ctx, g: g, server: server, authToken: g.authToken, internalAuth: g.internalAuth, logger: g.logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create implements transfer.LockBackend
|
// Create implements transfer.LockBackend
|
||||||
@ -45,8 +45,8 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) {
|
|||||||
}
|
}
|
||||||
url := g.server.String()
|
url := g.server.String()
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorisation: g.itoken,
|
headerAuthorization: g.authToken,
|
||||||
headerAuthX: g.token,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
@ -97,8 +97,8 @@ func (g *giteaLockBackend) Unlock(lock transfer.Lock) error {
|
|||||||
}
|
}
|
||||||
url := g.server.JoinPath(lock.ID(), "unlock").String()
|
url := g.server.JoinPath(lock.ID(), "unlock").String()
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorisation: g.itoken,
|
headerAuthorization: g.authToken,
|
||||||
headerAuthX: g.token,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
@ -180,8 +180,8 @@ func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, er
|
|||||||
urlq.RawQuery = v.Encode()
|
urlq.RawQuery = v.Encode()
|
||||||
url := urlq.String()
|
url := urlq.String()
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
headerAuthorisation: g.itoken,
|
headerAuthorization: g.authToken,
|
||||||
headerAuthX: g.token,
|
headerGiteaInternalAuth: g.internalAuth,
|
||||||
headerAccept: mimeGitLFS,
|
headerAccept: mimeGitLFS,
|
||||||
headerContentType: mimeGitLFS,
|
headerContentType: mimeGitLFS,
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user