Compare commits
61 commits
f649ef486c
...
2ae820da8a
Author | SHA1 | Date | |
---|---|---|---|
|
2ae820da8a | ||
29126931d8 | |||
abc1798d56 | |||
5b97a2a085 | |||
a19fff67ec | |||
d55e989c9c | |||
27081c173a | |||
|
73189f0a19 | ||
|
92f72d678c | ||
|
7e26f2b626 | ||
|
3d34cdabb9 | ||
|
f51a19c537 | ||
|
068e96fbd2 | ||
|
721e422fa7 | ||
|
6f323d13dd | ||
|
0e95e7460e | ||
|
c057590a3a | ||
|
a8534ac4a4 | ||
|
e23ad87b55 | ||
|
e93a4a0174 | ||
|
601766d1fa | ||
|
ee6d5124bd | ||
|
8188cdfcd2 | ||
|
82d50af721 | ||
|
6117c8b15a | ||
|
ba16df8da3 | ||
|
87630a6583 | ||
|
56716f5834 | ||
|
65b5c8e532 | ||
|
9dc53ba65f | ||
|
d25c74f353 | ||
|
795913e3c7 | ||
|
e609ef9585 | ||
|
f321cdced7 | ||
|
f241201484 | ||
|
43bddc1405 | ||
|
9414260d67 | ||
|
3c07ed0911 | ||
|
995ae06a6e | ||
|
14342047ad | ||
|
d6d62c071f | ||
|
7a2daae7c3 | ||
|
5bc3fbd511 | ||
|
b0a057f1c0 | ||
|
43a8547df6 | ||
|
291787a5ef | ||
|
e504410708 | ||
|
2ccf940464 | ||
|
169c08e20a | ||
|
d5856fece7 | ||
|
0571ddc368 | ||
|
6b7ce726c2 | ||
|
92b5f48c40 | ||
|
8043fbce09 | ||
|
556e2d5506 | ||
|
675c14aba6 | ||
|
4b4adb1cc9 | ||
|
19df07f021 | ||
|
5a84558e7c | ||
|
46053c092d | ||
|
3f032759ed |
95 changed files with 1131 additions and 404 deletions
27
.drone.yml
27
.drone.yml
|
@ -851,7 +851,8 @@ steps:
|
|||
image: plugins/hugo:latest
|
||||
pull: always
|
||||
commands:
|
||||
- apk add --no-cache make bash curl
|
||||
# https://github.com/drone-plugins/drone-hugo/issues/36
|
||||
- apk upgrade --no-cache libcurl && apk add --no-cache make bash curl
|
||||
- cd docs
|
||||
- make trans-copy clean build
|
||||
|
||||
|
@ -902,8 +903,11 @@ steps:
|
|||
image: techknowlogick/drone-docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
auto_tag_suffix: linux-amd64
|
||||
tags:
|
||||
- ${DRONE_TAG##v}-linux-amd64
|
||||
- ${DRONE_TAG:1:4}-linux-amd64
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
|
@ -920,8 +924,11 @@ steps:
|
|||
image: techknowlogick/drone-docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
auto_tag_suffix: linux-amd64-rootless
|
||||
tags:
|
||||
- ${DRONE_TAG##v}-linux-amd64-rootless
|
||||
- ${DRONE_TAG:1:4}-linux-amd64-rootless
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
|
@ -1126,8 +1133,11 @@ steps:
|
|||
image: techknowlogick/drone-docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
auto_tag_suffix: linux-arm64
|
||||
tags:
|
||||
- ${DRONE_TAG##v}-linux-arm64
|
||||
- ${DRONE_TAG:1:4}-linux-arm64
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
|
@ -1144,8 +1154,11 @@ steps:
|
|||
image: techknowlogick/drone-docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
auto_tag_suffix: linux-arm64-rootless
|
||||
tags:
|
||||
- ${DRONE_TAG##v}-linux-arm64-rootless
|
||||
- ${DRONE_TAG:1:4}-linux-arm64-rootless
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
|
@ -1299,7 +1312,7 @@ steps:
|
|||
image: plugins/manifest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
ignore_missing: true
|
||||
spec: docker/manifest.rootless.tmpl
|
||||
password:
|
||||
|
@ -1310,7 +1323,7 @@ steps:
|
|||
- name: manifest
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag: false
|
||||
ignore_missing: true
|
||||
spec: docker/manifest.tmpl
|
||||
password:
|
||||
|
|
39
.woodpecker.yml
Normal file
39
.woodpecker.yml
Normal file
|
@ -0,0 +1,39 @@
|
|||
clone:
|
||||
git:
|
||||
image: woodpeckerci/plugin-git
|
||||
settings:
|
||||
tags: true
|
||||
|
||||
pipeline:
|
||||
build:
|
||||
image: docker.io/alpine:3.16
|
||||
commands:
|
||||
- echo "172.17.0.1 alpine.proxy.coso npm.proxy.coso" >> /etc/hosts
|
||||
- echo "http://alpine.proxy.coso/alpine/v3.16/main" > /etc/apk/repositories
|
||||
- echo "http://alpine.proxy.coso/alpine/v3.16/community" >> /etc/apk/repositories
|
||||
- apk add git nodejs npm go make rsync openssh-client-default
|
||||
- npm set registry http://npm.proxy.coso
|
||||
|
||||
- GOOS=linux GOARCH=amd64 LDFLAGS="-linkmode external -extldflags '-static' $LDFLAGS" TAGS="bindata sqlite sqlite_unlock_notify" make build
|
||||
|
||||
- eval $(ssh-agent -s)
|
||||
- echo "$${SSH_KEY}" | tr -d '\r' | ssh-add -
|
||||
- mkdir -p ~/.ssh
|
||||
- echo "[nulo.in]:420,[186.136.121.7]:420 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGPkgRVWYcVcgjI0xAjDgZQsYuXU9edcya8zna01ibyUMlfKHIMD9yOoq0R+fQPTCqwiol/2tKMPJ2hlKshc+H8=" > ~/.ssh/known_hosts
|
||||
|
||||
# #!/bin/sh
|
||||
# # Editar en https://gitea.nulo.in/Nulo/gitea si se cambia
|
||||
# bin=/usr/local/bin/gitea
|
||||
# new=/usr/local/bin/gitea.new
|
||||
#
|
||||
# rm "$new"
|
||||
# cat /dev/stdin > "$new" || exit $?
|
||||
# chmod +x "$new" || exit $?
|
||||
# mv "$new" "$bin" || exit $?
|
||||
# sv restart gitea || exit $?
|
||||
- ssh -p420 ci-gitea@nulo.in doas /usr/local/sbin/instalar-gitea < gitea
|
||||
when:
|
||||
branch: gitea.nulo.in
|
||||
event: push
|
||||
secrets:
|
||||
- ssh_key
|
54
CHANGELOG.md
54
CHANGELOG.md
|
@ -4,6 +4,60 @@ This changelog goes through all the changes that have been made in each release
|
|||
without substantial changes to our git log; to see the highlights of what has
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
|
||||
## [1.17.4](https://github.com/go-gitea/gitea/releases/tag/1.17.4) - 2022-12-21
|
||||
|
||||
* SECURITY
|
||||
* Do not allow Ghost access to limited visible user/org (#21849) (#21875)
|
||||
* Fix package access for admins and inactive users (#21580) (#21592)
|
||||
* ENHANCEMENTS
|
||||
* Fix button in branch list, avoid unexpected page jump before restore branch actually done (#21562) (#21927)
|
||||
* Fix vertical align of committer avatar rendered by email address (#21884) (#21919)
|
||||
* Fix setting HTTP headers after write (#21833) (#21874)
|
||||
* Ignore line anchor links with leading zeroes (#21728) (#21777)
|
||||
* Enable Monaco automaticLayout (#21516)
|
||||
* BUGFIXES
|
||||
* Do not list active repositories as unadopted (#22034) (#22167)
|
||||
* Correctly handle moved files in apply patch (#22118) (#22136)
|
||||
* Fix condition for is_internal (#22095) (#22131)
|
||||
* Fix permission check on issue/pull lock (#22114)
|
||||
* Fix sorting admin user list by last login (#22081) (#22106)
|
||||
* Workaround for container registry push/pull errors (#21862) (#22069)
|
||||
* Fix issue/PR numbers (#22037) (#22045)
|
||||
* Handle empty author names (#21902) (#22028)
|
||||
* Fix ListBranches to handle empty case (#21921) (#22025)
|
||||
* Fix enabling partial clones on 1.17 (#21809)
|
||||
* Prevent panic in doctor command when running default checks (#21791) (#21808)
|
||||
* Upgrade golang.org/x/crypto (#21792) (#21794)
|
||||
* Init git module before database migration (#21764) (#21766)
|
||||
* Set last login when activating account (#21731) (#21754)
|
||||
* Add HEAD fix to gitea doctor (#21352) (#21751)
|
||||
* Fix UI language switching bug (#21597) (#21748)
|
||||
* Remove semver compatible flag and change pypi to an array of test cases (#21708) (#21729)
|
||||
* Allow local package identifiers for PyPI packages (#21690) (#21726)
|
||||
* Fix repository adoption on Windows (#21646) (#21651)
|
||||
* Sync git hooks when config file path changed (#21619) (#21625)
|
||||
* Added check for disabled Packages (#21540) (#21614)
|
||||
* Fix `Timestamp.IsZero` (#21593) (#21604)
|
||||
* Fix issues count bug (#21600)
|
||||
* Support binary deploy in npm packages (#21589)
|
||||
* Update milestone counters when issue is deleted (#21459) (#21586)
|
||||
* SessionUser protection against nil pointer dereference (#21581)
|
||||
* Case-insensitive NuGet symbol file GUID (#21409) (#21575)
|
||||
* Suppress `ExternalLoginUserNotExist` error (#21504) (#21572)
|
||||
* Prevent Authorization header for presigned LFS urls (#21531) (#21569)
|
||||
* Update binding to fix bugs (#21560)
|
||||
* Fix generating compare link (#21519) (#21530)
|
||||
* Ignore error when retrieving changed PR review files (#21487) (#21524)
|
||||
* Fix incorrect notification commit url (#21479) (#21483)
|
||||
* Display total commit count in hook message (#21400) (#21481)
|
||||
* Enforce grouped NuGet search results (#21442) (#21480)
|
||||
* Return 404 when user is not found on avatar (#21476) (#21477)
|
||||
* Normalize NuGet package version on upload (#22186) (#22201)
|
||||
* MISC
|
||||
* Check for zero time instant in TimeStamp.IsZero() (#22171) (#22173)
|
||||
* Fix warn in database structs sync (#22111)
|
||||
* Allow for resolution of NPM registry paths that match upstream (#21568) (#21723)
|
||||
|
||||
## [1.17.3](https://github.com/go-gitea/gitea/releases/tag/v1.17.3) - 2022-10-15
|
||||
|
||||
* SECURITY
|
||||
|
|
10
go.mod
10
go.mod
|
@ -5,7 +5,7 @@ go 1.18
|
|||
require (
|
||||
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b
|
||||
code.gitea.io/sdk/gitea v0.15.1
|
||||
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb
|
||||
gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681
|
||||
gitea.com/go-chi/cache v0.2.0
|
||||
gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5
|
||||
gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8
|
||||
|
@ -90,11 +90,11 @@ require (
|
|||
github.com/yuin/goldmark-meta v1.1.0
|
||||
go.jolheiser.com/hcaptcha v0.0.4
|
||||
go.jolheiser.com/pwn v0.0.3
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc
|
||||
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891
|
||||
golang.org/x/net v0.2.0
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10
|
||||
golang.org/x/text v0.3.8
|
||||
golang.org/x/sys v0.2.0
|
||||
golang.org/x/text v0.4.0
|
||||
golang.org/x/tools v0.1.12
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.66.4
|
||||
|
|
22
go.sum
22
go.sum
|
@ -69,8 +69,8 @@ contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EU
|
|||
contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
|
||||
contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb h1:Yy0Bxzc8R2wxiwXoG/rECGplJUSpXqCsog9PuJFgiHs=
|
||||
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
|
||||
gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681 h1:MMSPgnVULVwV9kEBgvyEUhC9v/uviZ55hPJEMjpbNR4=
|
||||
gitea.com/go-chi/binding v0.0.0-20221013104517-b29891619681/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
|
||||
gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
|
||||
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
|
||||
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
|
||||
|
@ -1676,8 +1676,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891 h1:WhEPFM1Ck5gaKybeSWvzI7Y/cd8K9K5tJGRxXMACOBA=
|
||||
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -1788,8 +1788,8 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -1936,12 +1936,12 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1951,8 +1951,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
|
@ -6,10 +6,12 @@ package integrations
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
@ -433,6 +435,10 @@ func TestPackageContainer(t *testing.T) {
|
|||
|
||||
assert.Equal(t, fmt.Sprintf("%d", len(blobContent)), resp.Header().Get("Content-Length"))
|
||||
assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest"))
|
||||
|
||||
req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, blobDigest))
|
||||
addTokenAuthHeader(req, anonymousToken)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("GetBlob", func(t *testing.T) {
|
||||
|
@ -545,6 +551,32 @@ func TestPackageContainer(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
t.Run("ParallelUpload", func(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
url := fmt.Sprintf("%sv2/%s/parallel", setting.AppURL, user.Name)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
|
||||
content := []byte{byte(i)}
|
||||
digest := fmt.Sprintf("sha256:%x", sha256.Sum256(content))
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, digest), bytes.NewReader(content))
|
||||
addTokenAuthHeader(req, userToken)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
assert.Equal(t, digest, resp.Header().Get("Docker-Content-Digest"))
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
t.Run("OwnerNameChange", func(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ func TestPackageNpm(t *testing.T) {
|
|||
packageTag2 := "release"
|
||||
packageAuthor := "KN4CK3R"
|
||||
packageDescription := "Test Description"
|
||||
packageBinName := "cli"
|
||||
packageBinPath := "./cli.sh"
|
||||
|
||||
data := "H4sIAAAAAAAA/ytITM5OTE/VL4DQelnF+XkMVAYGBgZmJiYK2MRBwNDcSIHB2NTMwNDQzMwAqA7IMDUxA9LUdgg2UFpcklgEdAql5kD8ogCnhwio5lJQUMpLzE1VslJQcihOzi9I1S9JLS7RhSYIJR2QgrLUouLM/DyQGkM9Az1D3YIiqExKanFyUWZBCVQ2BKhVwQVJDKwosbQkI78IJO/tZ+LsbRykxFXLNdA+HwWjYBSMgpENACgAbtAACAAA"
|
||||
upload := `{
|
||||
|
@ -51,6 +53,9 @@ func TestPackageNpm(t *testing.T) {
|
|||
"author": {
|
||||
"name": "` + packageAuthor + `"
|
||||
},
|
||||
"bin": {
|
||||
"` + packageBinName + `": "` + packageBinPath + `"
|
||||
},
|
||||
"dist": {
|
||||
"integrity": "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==",
|
||||
"shasum": "aaa7eaf852a948b0aa05afeda35b1badca155d90"
|
||||
|
@ -118,10 +123,16 @@ func TestPackageNpm(t *testing.T) {
|
|||
b, _ := base64.StdEncoding.DecodeString(data)
|
||||
assert.Equal(t, b, resp.Body.Bytes())
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/-/%s", root, filename))
|
||||
req = addTokenAuthHeader(req, token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.Equal(t, b, resp.Body.Bytes())
|
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pvs, 1)
|
||||
assert.Equal(t, int64(1), pvs[0].DownloadCount)
|
||||
assert.Equal(t, int64(2), pvs[0].DownloadCount)
|
||||
})
|
||||
|
||||
t.Run("PackageMetadata", func(t *testing.T) {
|
||||
|
@ -150,6 +161,7 @@ func TestPackageNpm(t *testing.T) {
|
|||
assert.Equal(t, packageName, pmv.Name)
|
||||
assert.Equal(t, packageDescription, pmv.Description)
|
||||
assert.Equal(t, packageAuthor, pmv.Author.Name)
|
||||
assert.Equal(t, packageBinPath, pmv.Bin[packageBinName])
|
||||
assert.Equal(t, "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==", pmv.Dist.Integrity)
|
||||
assert.Equal(t, "aaa7eaf852a948b0aa05afeda35b1badca155d90", pmv.Dist.Shasum)
|
||||
assert.Equal(t, fmt.Sprintf("%s%s/-/%s/%s", setting.AppURL, root[1:], packageVersion, filename), pmv.Dist.Tarball)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
|
@ -43,23 +44,29 @@ func TestPackageNuGet(t *testing.T) {
|
|||
symbolFilename := "test.pdb"
|
||||
symbolID := "d910bb6948bd4c6cb40155bcf52c3c94"
|
||||
|
||||
var buf bytes.Buffer
|
||||
archive := zip.NewWriter(&buf)
|
||||
w, _ := archive.Create("package.nuspec")
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>` + packageName + `</id>
|
||||
<version>` + packageVersion + `</version>
|
||||
<authors>` + packageAuthors + `</authors>
|
||||
<description>` + packageDescription + `</description>
|
||||
<group targetFramework=".NETStandard2.0">
|
||||
<dependency id="Microsoft.CSharp" version="4.5.0" />
|
||||
</group>
|
||||
</metadata>
|
||||
</package>`))
|
||||
archive.Close()
|
||||
content := buf.Bytes()
|
||||
createPackage := func(id, version string) io.Reader {
|
||||
var buf bytes.Buffer
|
||||
archive := zip.NewWriter(&buf)
|
||||
w, _ := archive.Create("package.nuspec")
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>` + id + `</id>
|
||||
<version>` + version + `</version>
|
||||
<authors>` + packageAuthors + `</authors>
|
||||
<description>` + packageDescription + `</description>
|
||||
<dependencies>
|
||||
<group targetFramework=".NETStandard2.0">
|
||||
<dependency id="Microsoft.CSharp" version="4.5.0" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
</package>`))
|
||||
archive.Close()
|
||||
return &buf
|
||||
}
|
||||
|
||||
content, _ := ioutil.ReadAll(createPackage(packageName, packageVersion))
|
||||
|
||||
url := fmt.Sprintf("/api/packages/%s/nuget", user.Name)
|
||||
|
||||
|
@ -159,7 +166,7 @@ func TestPackageNuGet(t *testing.T) {
|
|||
t.Run("SymbolPackage", func(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
createPackage := func(id, packageType string) io.Reader {
|
||||
createSymbolPackage := func(id, packageType string) io.Reader {
|
||||
var buf bytes.Buffer
|
||||
archive := zip.NewWriter(&buf)
|
||||
|
||||
|
@ -185,15 +192,15 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
|||
return &buf
|
||||
}
|
||||
|
||||
req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage("unknown-package", "SymbolsPackage"))
|
||||
req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage("unknown-package", "SymbolsPackage"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "DummyPackage"))
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "DummyPackage"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "SymbolsPackage"))
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "SymbolsPackage"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
|
@ -237,7 +244,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
|||
}
|
||||
}
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "SymbolsPackage"))
|
||||
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "SymbolsPackage"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusConflict)
|
||||
})
|
||||
|
@ -279,7 +286,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
|||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFFFFF/%s", url, symbolFilename, symbolID, symbolFilename))
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFffff/%s", url, symbolFilename, symbolID, symbolFilename))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
|
@ -315,6 +322,43 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
|||
assert.Equal(t, c.ExpectedTotal, result.TotalHits, "case %d: unexpected total hits", i)
|
||||
assert.Len(t, result.Data, c.ExpectedResults, "case %d: unexpected result count", i)
|
||||
}
|
||||
|
||||
t.Run("EnforceGrouped", func(t *testing.T) {
|
||||
defer PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequestWithBody(t, "PUT", url, createPackage(packageName+".dummy", "1.0.0"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
req = NewRequestWithBody(t, "PUT", url, createPackage(packageName, "1.0.99"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/query?q=%s", url, packageName))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var result nuget.SearchResultResponse
|
||||
DecodeJSON(t, resp, &result)
|
||||
|
||||
assert.EqualValues(t, 3, result.TotalHits)
|
||||
assert.Len(t, result.Data, 2)
|
||||
for _, sr := range result.Data {
|
||||
if sr.ID == packageName {
|
||||
assert.Len(t, sr.Versions, 2)
|
||||
} else {
|
||||
assert.Len(t, sr.Versions, 1)
|
||||
}
|
||||
}
|
||||
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName+".dummy", "1.0.0"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, "1.0.99"))
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("RegistrationService", func(t *testing.T) {
|
||||
|
|
|
@ -28,7 +28,7 @@ func TestPackagePyPI(t *testing.T) {
|
|||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||
|
||||
packageName := "test-package"
|
||||
packageVersion := "1.0.1"
|
||||
packageVersion := "1!1.0.1+r1234"
|
||||
packageAuthor := "KN4CK3R"
|
||||
packageDescription := "Test Description"
|
||||
|
||||
|
@ -71,7 +71,7 @@ func TestPackagePyPI(t *testing.T) {
|
|||
|
||||
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pd.SemVer)
|
||||
assert.Nil(t, pd.SemVer)
|
||||
assert.IsType(t, &pypi.Metadata{}, pd.Metadata)
|
||||
assert.Equal(t, packageName, pd.Package.Name)
|
||||
assert.Equal(t, packageVersion, pd.Version.Version)
|
||||
|
@ -99,7 +99,7 @@ func TestPackagePyPI(t *testing.T) {
|
|||
|
||||
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pd.SemVer)
|
||||
assert.Nil(t, pd.SemVer)
|
||||
assert.IsType(t, &pypi.Metadata{}, pd.Metadata)
|
||||
assert.Equal(t, packageName, pd.Package.Name)
|
||||
assert.Equal(t, packageVersion, pd.Version.Version)
|
||||
|
@ -163,7 +163,7 @@ func TestPackagePyPI(t *testing.T) {
|
|||
nodes := htmlDoc.doc.Find("a").Nodes
|
||||
assert.Len(t, nodes, 2)
|
||||
|
||||
hrefMatcher := regexp.MustCompile(fmt.Sprintf(`%s/files/%s/%s/test\..+#sha256-%s`, root, packageName, packageVersion, hashSHA256))
|
||||
hrefMatcher := regexp.MustCompile(fmt.Sprintf(`%s/files/%s/%s/test\..+#sha256-%s`, root, regexp.QuoteMeta(packageName), regexp.QuoteMeta(packageVersion), hashSHA256))
|
||||
|
||||
for _, a := range nodes {
|
||||
for _, att := range a.Attr {
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
func TestPackageAPI(t *testing.T) {
|
||||
defer prepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session)
|
||||
|
@ -143,6 +144,27 @@ func TestPackageAPI(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestPackageAccess(t *testing.T) {
|
||||
defer prepareTestEnv(t)()
|
||||
|
||||
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
|
||||
inactive := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 9}).(*user_model.User)
|
||||
|
||||
uploadPackage := func(doer, owner *user_model.User, expectedStatus int) {
|
||||
url := fmt.Sprintf("/api/packages/%s/generic/test-package/1.0/file.bin", owner.Name)
|
||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1}))
|
||||
AddBasicAuthHeader(req, doer.Name)
|
||||
MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
uploadPackage(user, inactive, http.StatusUnauthorized)
|
||||
uploadPackage(inactive, inactive, http.StatusUnauthorized)
|
||||
uploadPackage(inactive, user, http.StatusUnauthorized)
|
||||
uploadPackage(admin, inactive, http.StatusCreated)
|
||||
uploadPackage(admin, user, http.StatusCreated)
|
||||
}
|
||||
|
||||
func TestPackageCleanup(t *testing.T) {
|
||||
defer prepareTestEnv(t)()
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -430,19 +431,19 @@ var tokenCounter int64
|
|||
|
||||
func getTokenForLoggedInUser(t testing.TB, session *TestSession) string {
|
||||
t.Helper()
|
||||
tokenCounter++
|
||||
req := NewRequest(t, "GET", "/user/settings/applications")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
req = NewRequestWithValues(t, "POST", "/user/settings/applications", map[string]string{
|
||||
"_csrf": doc.GetCSRF(),
|
||||
"name": fmt.Sprintf("api-testing-token-%d", tokenCounter),
|
||||
"name": fmt.Sprintf("api-testing-token-%d", atomic.AddInt64(&tokenCounter, 1)),
|
||||
})
|
||||
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
req = NewRequest(t, "GET", "/user/settings/applications")
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
token := htmlDoc.doc.Find(".ui.info p").Text()
|
||||
assert.NotEmpty(t, token)
|
||||
return token
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,12 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
|
||||
const DefaultAvatarPixelSize = 28
|
||||
const (
|
||||
// DefaultAvatarClass is the default class of a rendered avatar
|
||||
DefaultAvatarClass = "ui avatar vm"
|
||||
// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
|
||||
DefaultAvatarPixelSize = 28
|
||||
)
|
||||
|
||||
// EmailHash represents a pre-generated hash map (mainly used by LibravatarURL, it queries email server's DNS records)
|
||||
type EmailHash struct {
|
||||
|
|
|
@ -881,7 +881,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
|
|||
}
|
||||
}
|
||||
case CommentTypeReopen, CommentTypeClose:
|
||||
if err = updateIssueClosedNum(ctx, opts.Issue); err != nil {
|
||||
if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -722,7 +722,8 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
|
|||
}
|
||||
}
|
||||
|
||||
if err := updateIssueClosedNum(ctx, issue); err != nil {
|
||||
// update repository's issue closed number
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -1006,12 +1007,7 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
|||
}
|
||||
}
|
||||
|
||||
if opts.IsPull {
|
||||
_, err = e.Exec("UPDATE `repository` SET num_pulls = num_pulls + 1 WHERE id = ?", opts.Issue.RepoID)
|
||||
} else {
|
||||
_, err = e.Exec("UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?", opts.Issue.RepoID)
|
||||
}
|
||||
if err != nil {
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.IsPull, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -2104,15 +2100,6 @@ func (issue *Issue) BlockingDependencies(ctx context.Context) (issueDeps []*Depe
|
|||
return issueDeps, err
|
||||
}
|
||||
|
||||
func updateIssueClosedNum(ctx context.Context, issue *Issue) (err error) {
|
||||
if issue.IsPull {
|
||||
err = repo_model.StatsCorrectNumClosed(ctx, issue.RepoID, true, "num_closed_pulls")
|
||||
} else {
|
||||
err = repo_model.StatsCorrectNumClosed(ctx, issue.RepoID, false, "num_closed_issues")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database.
|
||||
func FindAndUpdateIssueMentions(ctx context.Context, issue *Issue, doer *user_model.User, content string) (mentions []*user_model.User, err error) {
|
||||
rawMentions := references.FindAllMentionsMarkdown(content)
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
|
@ -496,6 +497,13 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
|
|||
return nil
|
||||
}
|
||||
|
||||
// Some migration tasks depend on the git command
|
||||
if git.DefaultContext == nil {
|
||||
if err = git.InitSimple(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate
|
||||
for i, m := range migrations[v-minDBVersion:] {
|
||||
log.Info("Migration[%d]: %s", v+int64(i), m.Description())
|
||||
|
|
|
@ -448,8 +448,9 @@ func CountOrgs(opts FindOrgOptions) (int64, error) {
|
|||
|
||||
// HasOrgOrUserVisible tells if the given user can see the given org or user
|
||||
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
||||
// Not SignedUser
|
||||
if user == nil {
|
||||
// If user is nil, it's an anonymous user/request.
|
||||
// The Ghost user is handled like an anonymous user.
|
||||
if user == nil || user.IsGhost() {
|
||||
return orgOrUser.Visibility == structs.VisibleTypePublic
|
||||
}
|
||||
|
||||
|
|
|
@ -289,7 +289,7 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
|
|||
|
||||
sess := db.GetEngine(ctx).
|
||||
Table("package_version").
|
||||
Join("LEFT", "package_version pv2", "package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))").
|
||||
Join("LEFT", "package_version pv2", "package_version.package_id = pv2.package_id AND pv2.is_internal = ? AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))", false).
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(cond)
|
||||
|
||||
|
|
|
@ -562,24 +562,19 @@ func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error {
|
|||
}
|
||||
|
||||
func repoStatsCorrectNumIssues(ctx context.Context, id int64) error {
|
||||
return repoStatsCorrectNum(ctx, id, false, "num_issues")
|
||||
return repo_model.UpdateRepoIssueNumbers(ctx, id, false, false)
|
||||
}
|
||||
|
||||
func repoStatsCorrectNumPulls(ctx context.Context, id int64) error {
|
||||
return repoStatsCorrectNum(ctx, id, true, "num_pulls")
|
||||
}
|
||||
|
||||
func repoStatsCorrectNum(ctx context.Context, id int64, isPull bool, field string) error {
|
||||
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_pull=?) WHERE id=?", id, isPull, id)
|
||||
return err
|
||||
return repo_model.UpdateRepoIssueNumbers(ctx, id, true, false)
|
||||
}
|
||||
|
||||
func repoStatsCorrectNumClosedIssues(ctx context.Context, id int64) error {
|
||||
return repo_model.StatsCorrectNumClosed(ctx, id, false, "num_closed_issues")
|
||||
return repo_model.UpdateRepoIssueNumbers(ctx, id, false, true)
|
||||
}
|
||||
|
||||
func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error {
|
||||
return repo_model.StatsCorrectNumClosed(ctx, id, true, "num_closed_pulls")
|
||||
return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true)
|
||||
}
|
||||
|
||||
func statsQuery(args ...interface{}) func(context.Context) ([]map[string][]byte, error) {
|
||||
|
@ -607,7 +602,7 @@ func CheckRepoStats(ctx context.Context) error {
|
|||
},
|
||||
// Repository.NumIssues
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", false, false),
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)", false),
|
||||
repoStatsCorrectNumIssues,
|
||||
"repository count 'num_issues'",
|
||||
},
|
||||
|
@ -619,7 +614,7 @@ func CheckRepoStats(ctx context.Context) error {
|
|||
},
|
||||
// Repository.NumPulls
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", false, true),
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)", true),
|
||||
repoStatsCorrectNumPulls,
|
||||
"repository count 'num_pulls'",
|
||||
},
|
||||
|
|
|
@ -23,6 +23,7 @@ type PushMirror struct {
|
|||
Repo *Repository `xorm:"-"`
|
||||
RemoteName string
|
||||
|
||||
SyncOnCommit bool `xorm:"NOT NULL DEFAULT true"`
|
||||
Interval time.Duration
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
|
||||
|
|
|
@ -23,6 +23,8 @@ import (
|
|||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ErrUserDoesNotHaveAccessToRepo represets an error where the user doesn't has access to a given repo.
|
||||
|
@ -319,13 +321,7 @@ func (repo *Repository) LoadUnits(ctx context.Context) (err error) {
|
|||
|
||||
// UnitEnabled if this repository has the given unit enabled
|
||||
func (repo *Repository) UnitEnabled(tp unit.Type) (result bool) {
|
||||
if err := db.WithContext(func(ctx *db.Context) error {
|
||||
result = repo.UnitEnabledCtx(ctx, tp)
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Error("repo.UnitEnabled: %v", err)
|
||||
}
|
||||
return
|
||||
return repo.UnitEnabledCtx(db.DefaultContext, tp)
|
||||
}
|
||||
|
||||
// UnitEnabled if this repository has the given unit enabled
|
||||
|
@ -760,33 +756,28 @@ func CountRepositories(ctx context.Context, opts CountRepositoryOptions) (int64,
|
|||
return count, nil
|
||||
}
|
||||
|
||||
// StatsCorrectNumClosed update repository's issue related numbers
|
||||
func StatsCorrectNumClosed(ctx context.Context, id int64, isPull bool, field string) error {
|
||||
_, err := db.Exec(ctx, "UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, isPull, id)
|
||||
// UpdateRepoIssueNumbers updates one of a repositories amount of (open|closed) (issues|PRs) with the current count
|
||||
func UpdateRepoIssueNumbers(ctx context.Context, repoID int64, isPull, isClosed bool) error {
|
||||
field := "num_"
|
||||
if isClosed {
|
||||
field += "closed_"
|
||||
}
|
||||
if isPull {
|
||||
field += "pulls"
|
||||
} else {
|
||||
field += "issues"
|
||||
}
|
||||
|
||||
subQuery := builder.Select("count(*)").
|
||||
From("issue").Where(builder.Eq{
|
||||
"repo_id": repoID,
|
||||
"is_pull": isPull,
|
||||
}.And(builder.If(isClosed, builder.Eq{"is_closed": isClosed})))
|
||||
|
||||
// builder.Update(cond) will generate SQL like UPDATE ... SET cond
|
||||
query := builder.Update(builder.Eq{field: subQuery}).
|
||||
From("repository").
|
||||
Where(builder.Eq{"id": repoID})
|
||||
_, err := db.Exec(ctx, query)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateRepoIssueNumbers update repository issue numbers
|
||||
func UpdateRepoIssueNumbers(ctx context.Context, repoID int64, isPull, isClosed bool) error {
|
||||
e := db.GetEngine(ctx)
|
||||
if isPull {
|
||||
if _, err := e.ID(repoID).Decr("num_pulls").Update(new(Repository)); err != nil {
|
||||
return err
|
||||
}
|
||||
if isClosed {
|
||||
if _, err := e.ID(repoID).Decr("num_closed_pulls").Update(new(Repository)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := e.ID(repoID).Decr("num_issues").Update(new(Repository)); err != nil {
|
||||
return err
|
||||
}
|
||||
if isClosed {
|
||||
if _, err := e.ID(repoID).Decr("num_closed_issues").Update(new(Repository)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -87,6 +87,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
|
|||
&user_model.Setting{UserID: u.ID},
|
||||
&pull_model.AutoMerge{DoerID: u.ID},
|
||||
&pull_model.ReviewState{UserID: u.ID},
|
||||
&user_model.Redirect{RedirectUserID: u.ID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %v", err)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@ package appstate
|
|||
|
||||
// RuntimeState contains app state for runtime, and we can save remote version for update checker here in future
|
||||
type RuntimeState struct {
|
||||
LastAppPath string `json:"last_app_path"`
|
||||
LastAppPath string `json:"last_app_path"`
|
||||
LastCustomConf string `json:"last_custom_conf"`
|
||||
}
|
||||
|
||||
// Name returns the item name
|
||||
|
|
|
@ -34,6 +34,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
|
@ -322,9 +323,9 @@ func (ctx *Context) plainTextInternal(skip, status int, bs []byte) {
|
|||
if statusPrefix == 4 || statusPrefix == 5 {
|
||||
log.Log(skip, log.TRACE, "plainTextInternal (status=%d): %s", status, string(bs))
|
||||
}
|
||||
ctx.Resp.WriteHeader(status)
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
||||
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
ctx.Resp.WriteHeader(status)
|
||||
if _, err := ctx.Resp.Write(bs); err != nil {
|
||||
log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
|
||||
}
|
||||
|
@ -345,16 +346,45 @@ func (ctx *Context) RespHeader() http.Header {
|
|||
return ctx.Resp.Header()
|
||||
}
|
||||
|
||||
type ServeHeaderOptions struct {
|
||||
ContentType string // defaults to "application/octet-stream"
|
||||
ContentTypeCharset string
|
||||
Disposition string // defaults to "attachment"
|
||||
Filename string
|
||||
CacheDuration time.Duration // defaults to 5 minutes
|
||||
}
|
||||
|
||||
// SetServeHeaders sets necessary content serve headers
|
||||
func (ctx *Context) SetServeHeaders(filename string) {
|
||||
ctx.Resp.Header().Set("Content-Description", "File Transfer")
|
||||
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
|
||||
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+filename)
|
||||
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
|
||||
ctx.Resp.Header().Set("Expires", "0")
|
||||
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
|
||||
ctx.Resp.Header().Set("Pragma", "public")
|
||||
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
|
||||
func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
|
||||
header := ctx.Resp.Header()
|
||||
|
||||
contentType := typesniffer.ApplicationOctetStream
|
||||
if opts.ContentType != "" {
|
||||
if opts.ContentTypeCharset != "" {
|
||||
contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset)
|
||||
} else {
|
||||
contentType = opts.ContentType
|
||||
}
|
||||
}
|
||||
header.Set("Content-Type", contentType)
|
||||
header.Set("X-Content-Type-Options", "nosniff")
|
||||
|
||||
if opts.Filename != "" {
|
||||
disposition := opts.Disposition
|
||||
if disposition == "" {
|
||||
disposition = "attachment"
|
||||
}
|
||||
|
||||
backslashEscapedName := strings.ReplaceAll(strings.ReplaceAll(opts.Filename, `\`, `\\`), `"`, `\"`) // \ -> \\, " -> \"
|
||||
header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"; filename*=UTF-8''%s`, disposition, backslashEscapedName, url.PathEscape(opts.Filename)))
|
||||
header.Set("Access-Control-Expose-Headers", "Content-Disposition")
|
||||
}
|
||||
|
||||
duration := opts.CacheDuration
|
||||
if duration == 0 {
|
||||
duration = 5 * time.Minute
|
||||
}
|
||||
httpcache.AddCacheControlToHeader(header, duration)
|
||||
}
|
||||
|
||||
// ServeContent serves content to http request
|
||||
|
@ -366,7 +396,9 @@ func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interfa
|
|||
modTime = v
|
||||
}
|
||||
}
|
||||
ctx.SetServeHeaders(name)
|
||||
ctx.SetServeHeaders(&ServeHeaderOptions{
|
||||
Filename: name,
|
||||
})
|
||||
http.ServeContent(ctx.Resp, ctx.Req, name, modTime, r)
|
||||
}
|
||||
|
||||
|
@ -378,13 +410,17 @@ func (ctx *Context) ServeFile(file string, names ...string) {
|
|||
} else {
|
||||
name = path.Base(file)
|
||||
}
|
||||
ctx.SetServeHeaders(name)
|
||||
ctx.SetServeHeaders(&ServeHeaderOptions{
|
||||
Filename: name,
|
||||
})
|
||||
http.ServeFile(ctx.Resp, ctx.Req, file)
|
||||
}
|
||||
|
||||
// ServeStream serves file via io stream
|
||||
func (ctx *Context) ServeStream(rd io.Reader, name string) {
|
||||
ctx.SetServeHeaders(name)
|
||||
ctx.SetServeHeaders(&ServeHeaderOptions{
|
||||
Filename: name,
|
||||
})
|
||||
_, err := io.Copy(ctx.Resp, rd)
|
||||
if err != nil {
|
||||
ctx.ServerError("Download file failed", err)
|
||||
|
|
|
@ -83,12 +83,15 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
|
|||
}
|
||||
|
||||
func determineAccessMode(ctx *Context) (perm.AccessMode, error) {
|
||||
accessMode := perm.AccessModeNone
|
||||
|
||||
if setting.Service.RequireSignInView && ctx.Doer == nil {
|
||||
return accessMode, nil
|
||||
return perm.AccessModeNone, nil
|
||||
}
|
||||
|
||||
if ctx.Doer != nil && !ctx.Doer.IsGhost() && (!ctx.Doer.IsActive || ctx.Doer.ProhibitLogin) {
|
||||
return perm.AccessModeNone, nil
|
||||
}
|
||||
|
||||
accessMode := perm.AccessModeNone
|
||||
if ctx.Package.Owner.IsOrganization() {
|
||||
org := organization.OrgFromUser(ctx.Package.Owner)
|
||||
|
||||
|
|
|
@ -205,6 +205,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
|
|||
// find stopwatches without existing issue
|
||||
genericOrphanCheck("Orphaned Stopwatches without existing Issue",
|
||||
"stopwatch", "issue", "stopwatch.issue_id=`issue`.id"),
|
||||
// find redirects without existing user.
|
||||
genericOrphanCheck("Orphaned Redirects without existing redirect user",
|
||||
"user_redirect", "user", "user_redirect.redirect_user_id=`user`.id"),
|
||||
)
|
||||
|
||||
for _, c := range consistencyChecks {
|
||||
|
|
89
modules/doctor/heads.go
Normal file
89
modules/doctor/heads.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package doctor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool) error {
|
||||
numRepos := 0
|
||||
numHeadsBroken := 0
|
||||
numDefaultBranchesBroken := 0
|
||||
numReposUpdated := 0
|
||||
err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
|
||||
numRepos++
|
||||
_, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse", "--", repo.DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
|
||||
|
||||
head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
|
||||
|
||||
// what we expect: default branch is valid, and HEAD points to it
|
||||
if headErr == nil && defaultBranchErr == nil && head == repo.DefaultBranch {
|
||||
return nil
|
||||
}
|
||||
|
||||
if headErr != nil {
|
||||
numHeadsBroken++
|
||||
}
|
||||
if defaultBranchErr != nil {
|
||||
numDefaultBranchesBroken++
|
||||
}
|
||||
|
||||
// if default branch is broken, let the user fix that in the UI
|
||||
if defaultBranchErr != nil {
|
||||
logger.Warn("Default branch for %s/%s doesn't point to a valid commit", repo.OwnerName, repo.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we're not autofixing, that's all we can do
|
||||
if !autofix {
|
||||
return nil
|
||||
}
|
||||
|
||||
// otherwise, let's try fixing HEAD
|
||||
err := git.NewCommand(ctx, "symbolic-ref", "--", "HEAD", git.BranchPrefix+repo.DefaultBranch).Run(&git.RunOpts{Dir: repo.RepoPath()})
|
||||
if err != nil {
|
||||
logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err)
|
||||
return nil
|
||||
}
|
||||
numReposUpdated++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logger.Critical("Error when fixing repo HEADs: %v", err)
|
||||
}
|
||||
|
||||
if autofix {
|
||||
logger.Info("Out of %d repos, HEADs for %d are now fixed and HEADS for %d are still broken", numRepos, numReposUpdated, numDefaultBranchesBroken+numHeadsBroken-numReposUpdated)
|
||||
} else {
|
||||
if numHeadsBroken == 0 && numDefaultBranchesBroken == 0 {
|
||||
logger.Info("All %d repos have their HEADs in the correct state", numRepos)
|
||||
} else {
|
||||
if numHeadsBroken == 0 && numDefaultBranchesBroken != 0 {
|
||||
logger.Critical("Default branches are broken for %d/%d repos", numDefaultBranchesBroken, numRepos)
|
||||
} else if numHeadsBroken != 0 && numDefaultBranchesBroken == 0 {
|
||||
logger.Warn("HEADs are broken for %d/%d repos", numHeadsBroken, numRepos)
|
||||
} else {
|
||||
logger.Critical("Out of %d repos, HEADS are broken for %d and default branches are broken for %d", numRepos, numHeadsBroken, numDefaultBranchesBroken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(&Check{
|
||||
Title: "Synchronize repo HEADs",
|
||||
Name: "synchronize-repo-heads",
|
||||
IsDefault: true,
|
||||
Run: synchronizeRepoHeads,
|
||||
Priority: 7,
|
||||
})
|
||||
}
|
|
@ -169,8 +169,11 @@ func (c *Command) Run(opts *RunOpts) error {
|
|||
if opts == nil {
|
||||
opts = &RunOpts{}
|
||||
}
|
||||
if opts.Timeout <= 0 {
|
||||
opts.Timeout = defaultCommandExecutionTimeout
|
||||
|
||||
// We must not change the provided options
|
||||
timeout := opts.Timeout
|
||||
if timeout <= 0 {
|
||||
timeout = defaultCommandExecutionTimeout
|
||||
}
|
||||
|
||||
if len(opts.Dir) == 0 {
|
||||
|
@ -205,7 +208,7 @@ func (c *Command) Run(opts *RunOpts) error {
|
|||
if opts.UseContextTimeout {
|
||||
ctx, cancel, finished = process.GetManager().AddContext(c.parentContext, desc)
|
||||
} else {
|
||||
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, opts.Timeout, desc)
|
||||
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, timeout, desc)
|
||||
}
|
||||
defer finished()
|
||||
|
||||
|
@ -306,9 +309,20 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
|
|||
}
|
||||
stdoutBuf := &bytes.Buffer{}
|
||||
stderrBuf := &bytes.Buffer{}
|
||||
opts.Stdout = stdoutBuf
|
||||
opts.Stderr = stderrBuf
|
||||
err := c.Run(opts)
|
||||
|
||||
// We must not change the provided options as it could break future calls - therefore make a copy.
|
||||
newOpts := &RunOpts{
|
||||
Env: opts.Env,
|
||||
Timeout: opts.Timeout,
|
||||
UseContextTimeout: opts.UseContextTimeout,
|
||||
Dir: opts.Dir,
|
||||
Stdout: stdoutBuf,
|
||||
Stderr: stderrBuf,
|
||||
Stdin: opts.Stdin,
|
||||
PipelineFunc: opts.PipelineFunc,
|
||||
}
|
||||
|
||||
err := c.Run(newOpts)
|
||||
stderr = stderrBuf.Bytes()
|
||||
if err != nil {
|
||||
return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)}
|
||||
|
|
|
@ -190,11 +190,6 @@ func InitOnceWithSync(ctx context.Context) (err error) {
|
|||
globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
|
||||
}
|
||||
|
||||
// By default partial clones are disabled, enable them from git v2.22
|
||||
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true", "-c", "uploadpack.allowAnySHA1InWant=true")
|
||||
}
|
||||
|
||||
// Explicitly disable credential helper, otherwise Git credentials might leak
|
||||
if CheckGitVersionAtLeast("2.9") == nil {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
|
||||
|
|
|
@ -53,7 +53,7 @@ func (repo *Repository) IsReferenceExist(name string) bool {
|
|||
|
||||
// IsBranchExist returns true if given branch exists in current repository.
|
||||
func (repo *Repository) IsBranchExist(name string) bool {
|
||||
if name == "" {
|
||||
if repo == nil || name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
|
||||
// IsTagExist returns true if given tag exists in the repository.
|
||||
func (repo *Repository) IsTagExist(name string) bool {
|
||||
if name == "" {
|
||||
if repo == nil || name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ package git
|
|||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
|
@ -30,7 +31,9 @@ type Signature = object.Signature
|
|||
func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
|
||||
sig := new(Signature)
|
||||
emailStart := bytes.IndexByte(line, '<')
|
||||
sig.Name = string(line[:emailStart-1])
|
||||
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
|
||||
sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
|
||||
}
|
||||
emailEnd := bytes.IndexByte(line, '>')
|
||||
sig.Email = string(line[emailStart+1 : emailEnd])
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -51,7 +52,9 @@ func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
sig.Name = string(line[:emailStart-1])
|
||||
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
|
||||
sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
|
||||
}
|
||||
sig.Email = string(line[emailStart+1 : emailEnd])
|
||||
|
||||
hasTime := emailEnd+2 < len(line)
|
||||
|
|
|
@ -315,13 +315,5 @@ func IsMarkupFile(name, markup string) bool {
|
|||
// Note that the '.' should be provided in ext, e.g ".md"
|
||||
func IsReadmeFile(name string, ext ...string) bool {
|
||||
name = strings.ToLower(name)
|
||||
if len(ext) > 0 {
|
||||
return name == "readme"+ext[0]
|
||||
}
|
||||
if len(name) < 6 {
|
||||
return false
|
||||
} else if len(name) == 6 {
|
||||
return name == "readme"
|
||||
}
|
||||
return name[:7] == "readme."
|
||||
return name == "readme" || strings.Index(name, "readme.") == 0 || strings.Index(name, ".readme.") == 0
|
||||
}
|
||||
|
|
|
@ -608,15 +608,16 @@ func (m *webhookNotifier) NotifyPushCommits(pusher *user_model.User, repo *repo_
|
|||
}
|
||||
|
||||
if err := webhook_services.PrepareWebhooks(repo, webhook.HookEventPush, &api.PushPayload{
|
||||
Ref: opts.RefFullName,
|
||||
Before: opts.OldCommitID,
|
||||
After: opts.NewCommitID,
|
||||
CompareURL: setting.AppURL + commits.CompareURL,
|
||||
Commits: apiCommits,
|
||||
HeadCommit: apiHeadCommit,
|
||||
Repo: convert.ToRepo(repo, perm.AccessModeOwner),
|
||||
Pusher: apiPusher,
|
||||
Sender: apiPusher,
|
||||
Ref: opts.RefFullName,
|
||||
Before: opts.OldCommitID,
|
||||
After: opts.NewCommitID,
|
||||
CompareURL: setting.AppURL + commits.CompareURL,
|
||||
Commits: apiCommits,
|
||||
TotalCommits: commits.Len,
|
||||
HeadCommit: apiHeadCommit,
|
||||
Repo: convert.ToRepo(repo, perm.AccessModeOwner),
|
||||
Pusher: apiPusher,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks: %v", err)
|
||||
}
|
||||
|
@ -838,15 +839,16 @@ func (m *webhookNotifier) NotifySyncPushCommits(pusher *user_model.User, repo *r
|
|||
}
|
||||
|
||||
if err := webhook_services.PrepareWebhooks(repo, webhook.HookEventPush, &api.PushPayload{
|
||||
Ref: opts.RefFullName,
|
||||
Before: opts.OldCommitID,
|
||||
After: opts.NewCommitID,
|
||||
CompareURL: setting.AppURL + commits.CompareURL,
|
||||
Commits: apiCommits,
|
||||
HeadCommit: apiHeadCommit,
|
||||
Repo: convert.ToRepo(repo, perm.AccessModeOwner),
|
||||
Pusher: apiPusher,
|
||||
Sender: apiPusher,
|
||||
Ref: opts.RefFullName,
|
||||
Before: opts.OldCommitID,
|
||||
After: opts.NewCommitID,
|
||||
CompareURL: setting.AppURL + commits.CompareURL,
|
||||
Commits: apiCommits,
|
||||
TotalCommits: commits.Len,
|
||||
HeadCommit: apiHeadCommit,
|
||||
Repo: convert.ToRepo(repo, perm.AccessModeOwner),
|
||||
Pusher: apiPusher,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks: %v", err)
|
||||
}
|
||||
|
|
|
@ -30,6 +30,13 @@ func (s *ContentStore) Get(key BlobHash256Key) (storage.Object, error) {
|
|||
return s.store.Open(KeyToRelativePath(key))
|
||||
}
|
||||
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
func (s *ContentStore) Has(key BlobHash256Key) error {
|
||||
_, err := s.store.Stat(KeyToRelativePath(key))
|
||||
return err
|
||||
}
|
||||
|
||||
// Save stores a package blob
|
||||
func (s *ContentStore) Save(key BlobHash256Key, r io.Reader, size int64) error {
|
||||
_, err := s.store.Save(KeyToRelativePath(key), r, size)
|
||||
|
|
|
@ -66,7 +66,8 @@ type PackageMetadata struct {
|
|||
License string `json:"license,omitempty"`
|
||||
}
|
||||
|
||||
// PackageMetadataVersion https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
|
||||
// PackageMetadataVersion documentation: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
|
||||
// PackageMetadataVersion response: https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-version-object
|
||||
type PackageMetadataVersion struct {
|
||||
ID string `json:"_id"`
|
||||
Name string `json:"name"`
|
||||
|
@ -80,6 +81,7 @@ type PackageMetadataVersion struct {
|
|||
Dependencies map[string]string `json:"dependencies,omitempty"`
|
||||
DevDependencies map[string]string `json:"devDependencies,omitempty"`
|
||||
PeerDependencies map[string]string `json:"peerDependencies,omitempty"`
|
||||
Bin map[string]string `json:"bin,omitempty"`
|
||||
OptionalDependencies map[string]string `json:"optionalDependencies,omitempty"`
|
||||
Readme string `json:"readme,omitempty"`
|
||||
Dist PackageDistribution `json:"dist"`
|
||||
|
@ -192,6 +194,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
|
|||
DevelopmentDependencies: meta.DevDependencies,
|
||||
PeerDependencies: meta.PeerDependencies,
|
||||
OptionalDependencies: meta.OptionalDependencies,
|
||||
Bin: meta.Bin,
|
||||
Readme: meta.Readme,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ func TestParsePackage(t *testing.T) {
|
|||
packageVersion := "1.0.1-pre"
|
||||
packageTag := "latest"
|
||||
packageAuthor := "KN4CK3R"
|
||||
packageBin := "gitea"
|
||||
packageDescription := "Test Description"
|
||||
data := "H4sIAAAAAAAA/ytITM5OTE/VL4DQelnF+XkMVAYGBgZmJiYK2MRBwNDcSIHB2NTMwNDQzMwAqA7IMDUxA9LUdgg2UFpcklgEdAql5kD8ogCnhwio5lJQUMpLzE1VslJQcihOzi9I1S9JLS7RhSYIJR2QgrLUouLM/DyQGkM9Az1D3YIiqExKanFyUWZBCVQ2BKhVwQVJDKwosbQkI78IJO/tZ+LsbRykxFXLNdA+HwWjYBSMgpENACgAbtAACAAA"
|
||||
integrity := "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg=="
|
||||
|
@ -236,6 +237,9 @@ func TestParsePackage(t *testing.T) {
|
|||
Dependencies: map[string]string{
|
||||
"package": "1.2.0",
|
||||
},
|
||||
Bin: map[string]string{
|
||||
"bin": packageBin,
|
||||
},
|
||||
Dist: PackageDistribution{
|
||||
Integrity: integrity,
|
||||
},
|
||||
|
@ -264,6 +268,7 @@ func TestParsePackage(t *testing.T) {
|
|||
assert.Equal(t, packageDescription, p.Metadata.Description)
|
||||
assert.Equal(t, packageDescription, p.Metadata.Readme)
|
||||
assert.Equal(t, packageAuthor, p.Metadata.Author)
|
||||
assert.Equal(t, packageBin, p.Metadata.Bin["bin"])
|
||||
assert.Equal(t, "MIT", p.Metadata.License)
|
||||
assert.Equal(t, "https://gitea.io/", p.Metadata.ProjectURL)
|
||||
assert.Contains(t, p.Metadata.Dependencies, "package")
|
||||
|
|
|
@ -20,5 +20,6 @@ type Metadata struct {
|
|||
DevelopmentDependencies map[string]string `json:"development_dependencies,omitempty"`
|
||||
PeerDependencies map[string]string `json:"peer_dependencies,omitempty"`
|
||||
OptionalDependencies map[string]string `json:"optional_dependencies,omitempty"`
|
||||
Bin map[string]string `json:"bin,omitempty"`
|
||||
Readme string `json:"readme,omitempty"`
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ package nuget
|
|||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
@ -181,7 +183,23 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
|
|||
return &Package{
|
||||
PackageType: packageType,
|
||||
ID: p.Metadata.ID,
|
||||
Version: v.String(),
|
||||
Version: toNormalizedVersion(v),
|
||||
Metadata: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#normalized-version-numbers
|
||||
// https://github.com/NuGet/NuGet.Client/blob/dccbd304b11103e08b97abf4cf4bcc1499d9235a/src/NuGet.Core/NuGet.Versioning/VersionFormatter.cs#L121
|
||||
func toNormalizedVersion(v *version.Version) string {
|
||||
var buf bytes.Buffer
|
||||
segments := v.Segments64()
|
||||
fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
|
||||
if len(segments) > 3 && segments[3] > 0 {
|
||||
fmt.Fprintf(&buf, ".%d", segments[3])
|
||||
}
|
||||
pre := v.Prerelease()
|
||||
if pre != "" {
|
||||
fmt.Fprint(&buf, "-", pre)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
|
@ -147,6 +147,19 @@ func TestParseNuspecMetaData(t *testing.T) {
|
|||
assert.Len(t, deps, 1)
|
||||
assert.Equal(t, dependencyID, deps[0].ID)
|
||||
assert.Equal(t, dependencyVersion, deps[0].Version)
|
||||
|
||||
t.Run("NormalizedVersion", func(t *testing.T) {
|
||||
np, err := ParseNuspecMetaData(strings.NewReader(`<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>test</id>
|
||||
<version>1.04.5.2.5-rc.1+metadata</version>
|
||||
</metadata>
|
||||
</package>`))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, np)
|
||||
assert.Equal(t, "1.4.5.2-rc.1", np.Version)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Symbols Package", func(t *testing.T) {
|
||||
|
|
|
@ -102,6 +102,10 @@ func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error)
|
|||
if err := util.Rename(tmp.Name(), p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Golang's tmp file (os.CreateTemp) always have 0o600 mode, so we need to change the file to follow the umask (as what Create/MkDir does)
|
||||
if err := util.ApplyUmask(p, os.ModePerm); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
tmpRemoved = true
|
||||
|
||||
|
|
|
@ -267,15 +267,16 @@ func (p *ReleasePayload) JSONPayload() ([]byte, error) {
|
|||
|
||||
// PushPayload represents a payload information of push event.
|
||||
type PushPayload struct {
|
||||
Ref string `json:"ref"`
|
||||
Before string `json:"before"`
|
||||
After string `json:"after"`
|
||||
CompareURL string `json:"compare_url"`
|
||||
Commits []*PayloadCommit `json:"commits"`
|
||||
HeadCommit *PayloadCommit `json:"head_commit"`
|
||||
Repo *Repository `json:"repository"`
|
||||
Pusher *User `json:"pusher"`
|
||||
Sender *User `json:"sender"`
|
||||
Ref string `json:"ref"`
|
||||
Before string `json:"before"`
|
||||
After string `json:"after"`
|
||||
CompareURL string `json:"compare_url"`
|
||||
Commits []*PayloadCommit `json:"commits"`
|
||||
TotalCommits int `json:"total_commits"`
|
||||
HeadCommit *PayloadCommit `json:"head_commit"`
|
||||
Repo *Repository `json:"repository"`
|
||||
Pusher *User `json:"pusher"`
|
||||
Sender *User `json:"sender"`
|
||||
}
|
||||
|
||||
// JSONPayload FIXME
|
||||
|
|
|
@ -458,6 +458,19 @@ func NewFuncMap() []template.FuncMap {
|
|||
return items
|
||||
},
|
||||
"HasPrefix": strings.HasPrefix,
|
||||
"CompareLink": func(baseRepo, repo *repo_model.Repository, branchName string) string {
|
||||
var curBranch string
|
||||
if repo.ID != baseRepo.ID {
|
||||
curBranch += fmt.Sprintf("%s/%s:", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name))
|
||||
}
|
||||
curBranch += util.PathEscapeSegments(branchName)
|
||||
|
||||
return fmt.Sprintf("%s/compare/%s...%s",
|
||||
baseRepo.Link(),
|
||||
util.PathEscapeSegments(baseRepo.DefaultBranch),
|
||||
curBranch,
|
||||
)
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -634,7 +647,7 @@ func SVG(icon string, others ...interface{}) template.HTML {
|
|||
|
||||
// Avatar renders user avatars. args: user, size (int), class (string)
|
||||
func Avatar(item interface{}, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar image", others...)
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
|
||||
|
||||
switch t := item.(type) {
|
||||
case *user_model.User:
|
||||
|
@ -665,7 +678,7 @@ func AvatarByAction(action *models.Action, others ...interface{}) template.HTML
|
|||
|
||||
// RepoAvatar renders repo avatars. args: repo, size(int), class (string)
|
||||
func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar image", others...)
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
|
||||
|
||||
src := repo.RelAvatarLink()
|
||||
if src != "" {
|
||||
|
@ -676,7 +689,7 @@ func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTM
|
|||
|
||||
// AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string)
|
||||
func AvatarByEmail(email, name string, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar image", others...)
|
||||
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
|
||||
src := avatars.GenerateEmailAvatarFastLink(email, size*setting.Avatar.RenderedSizeFactor)
|
||||
|
||||
if src != "" {
|
||||
|
|
|
@ -13,8 +13,13 @@ import (
|
|||
// TimeStamp defines a timestamp
|
||||
type TimeStamp int64
|
||||
|
||||
// mock is NOT concurrency-safe!!
|
||||
var mock time.Time
|
||||
var (
|
||||
// mock is NOT concurrency-safe!!
|
||||
mock time.Time
|
||||
|
||||
// Used for IsZero, to check if timestamp is the zero time instant.
|
||||
timeZeroUnix = time.Time{}.Unix()
|
||||
)
|
||||
|
||||
// Set sets the time to a mocked time.Time
|
||||
func Set(now time.Time) {
|
||||
|
@ -103,5 +108,5 @@ func (ts TimeStamp) FormatDate() string {
|
|||
|
||||
// IsZero is zero time
|
||||
func (ts TimeStamp) IsZero() bool {
|
||||
return ts.AsTimeInLocation(time.Local).IsZero()
|
||||
return int64(ts) == 0 || int64(ts) == timeZeroUnix
|
||||
}
|
||||
|
|
28
modules/util/file_unix.go
Normal file
28
modules/util/file_unix.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var defaultUmask int
|
||||
|
||||
func init() {
|
||||
// at the moment, the umask could only be gotten by calling unix.Umask(newUmask)
|
||||
// use 0o077 as temp new umask to reduce the risks if this umask is used anywhere else before the correct umask is recovered
|
||||
tempUmask := 0o077
|
||||
defaultUmask = unix.Umask(tempUmask)
|
||||
unix.Umask(defaultUmask)
|
||||
}
|
||||
|
||||
func ApplyUmask(f string, newMode os.FileMode) error {
|
||||
mod := newMode & ^os.FileMode(defaultUmask)
|
||||
return os.Chmod(f, mod)
|
||||
}
|
36
modules/util/file_unix_test.go
Normal file
36
modules/util/file_unix_test.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestApplyUmask(t *testing.T) {
|
||||
f, err := os.CreateTemp(t.TempDir(), "test-filemode-")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.Chmod(f.Name(), 0o777)
|
||||
assert.NoError(t, err)
|
||||
st, err := os.Stat(f.Name())
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0o777, st.Mode().Perm()&0o777)
|
||||
|
||||
oldDefaultUmask := defaultUmask
|
||||
defaultUmask = 0o037
|
||||
defer func() {
|
||||
defaultUmask = oldDefaultUmask
|
||||
}()
|
||||
err = ApplyUmask(f.Name(), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
st, err = os.Stat(f.Name())
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0o740, st.Mode().Perm()&0o777)
|
||||
}
|
16
modules/util/file_windows.go
Normal file
16
modules/util/file_windows.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func ApplyUmask(f string, newMode os.FileMode) error {
|
||||
// do nothing for Windows, because Windows doesn't use umask
|
||||
return nil
|
||||
}
|
|
@ -55,6 +55,7 @@ func Routes() *web.Route {
|
|||
authGroup := auth.NewGroup(authMethods...)
|
||||
r.Use(func(ctx *context.Context) {
|
||||
ctx.Doer = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
ctx.IsSigned = ctx.Doer != nil
|
||||
})
|
||||
|
||||
r.Group("/{username}", func() {
|
||||
|
@ -190,7 +191,7 @@ func Routes() *web.Route {
|
|||
r.Put("/symbolpackage", nuget.UploadSymbolPackage)
|
||||
r.Delete("/{id}/{version}", nuget.DeletePackage)
|
||||
}, reqPackageAccess(perm.AccessModeWrite))
|
||||
r.Get("/symbols/{filename}/{guid:[0-9a-f]{32}}FFFFFFFF/{filename2}", nuget.DownloadSymbolFile)
|
||||
r.Get("/symbols/{filename}/{guid:[0-9a-fA-F]{32}[fF]{8}}/{filename2}", nuget.DownloadSymbolFile)
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
})
|
||||
r.Group("/npm", func() {
|
||||
|
@ -198,11 +199,13 @@ func Routes() *web.Route {
|
|||
r.Get("", npm.PackageMetadata)
|
||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
|
||||
r.Get("/-/{version}/{filename}", npm.DownloadPackageFile)
|
||||
r.Get("/-/{filename}", npm.DownloadPackageFileByName)
|
||||
})
|
||||
r.Group("/{id}", func() {
|
||||
r.Get("", npm.PackageMetadata)
|
||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
|
||||
r.Get("/-/{version}/{filename}", npm.DownloadPackageFile)
|
||||
r.Get("/-/{filename}", npm.DownloadPackageFileByName)
|
||||
})
|
||||
r.Group("/-/package/@{scope}/{id}/dist-tags", func() {
|
||||
r.Get("", npm.ListPackageTags)
|
||||
|
@ -256,6 +259,7 @@ func ContainerRoutes() *web.Route {
|
|||
authGroup := auth.NewGroup(authMethods...)
|
||||
r.Use(func(ctx *context.Context) {
|
||||
ctx.Doer = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
||||
ctx.IsSigned = ctx.Doer != nil
|
||||
})
|
||||
|
||||
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
|
||||
|
|
|
@ -7,8 +7,11 @@ package container
|
|||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
|
@ -19,6 +22,8 @@ import (
|
|||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
)
|
||||
|
||||
var uploadVersionMutex sync.Mutex
|
||||
|
||||
// saveAsPackageBlob creates a package blob from an upload
|
||||
// The uploaded blob gets stored in a special upload version to link them to the package/image
|
||||
func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_service.PackageInfo) (*packages_model.PackageBlob, error) {
|
||||
|
@ -28,6 +33,11 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
|||
|
||||
contentStore := packages_module.NewContentStore()
|
||||
|
||||
var uploadVersion *packages_model.PackageVersion
|
||||
|
||||
// FIXME: Replace usage of mutex with database transaction
|
||||
// https://github.com/go-gitea/gitea/pull/21862
|
||||
uploadVersionMutex.Lock()
|
||||
err := db.WithTx(func(ctx context.Context) error {
|
||||
created := true
|
||||
p := &packages_model.Package{
|
||||
|
@ -68,11 +78,30 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
|||
}
|
||||
}
|
||||
|
||||
uploadVersion = pv
|
||||
|
||||
return nil
|
||||
})
|
||||
uploadVersionMutex.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.WithTx(func(ctx context.Context) error {
|
||||
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
|
||||
if err != nil {
|
||||
log.Error("Error inserting package blob: %v", err)
|
||||
return err
|
||||
}
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
if exists {
|
||||
err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
|
||||
exists = false
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
|
||||
log.Error("Error saving package blob in content store: %v", err)
|
||||
|
@ -83,7 +112,7 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
|||
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
|
||||
|
||||
pf := &packages_model.PackageFile{
|
||||
VersionID: pv.ID,
|
||||
VersionID: uploadVersion.ID,
|
||||
BlobID: pb.ID,
|
||||
Name: filename,
|
||||
LowerName: filename,
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -193,7 +194,7 @@ func InitiateUploadBlob(ctx *context.Context) {
|
|||
mount := ctx.FormTrim("mount")
|
||||
from := ctx.FormTrim("from")
|
||||
if mount != "" {
|
||||
blob, _ := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||
blob, _ := workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||
Image: from,
|
||||
Digest: mount,
|
||||
})
|
||||
|
@ -361,7 +362,7 @@ func getBlobFromContext(ctx *context.Context) (*packages_model.PackageFileDescri
|
|||
return nil, container_model.ErrContainerBlobNotExist
|
||||
}
|
||||
|
||||
return container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||
return workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||
OwnerID: ctx.Package.Owner.ID,
|
||||
Image: ctx.Params("image"),
|
||||
Digest: digest,
|
||||
|
@ -503,7 +504,7 @@ func getManifestFromContext(ctx *context.Context) (*packages_model.PackageFileDe
|
|||
return nil, container_model.ErrContainerBlobNotExist
|
||||
}
|
||||
|
||||
return container_model.GetContainerBlob(ctx, opts)
|
||||
return workaroundGetContainerBlob(ctx, opts)
|
||||
}
|
||||
|
||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#checking-if-content-exists-in-the-registry
|
||||
|
@ -643,3 +644,23 @@ func GetTagList(ctx *context.Context) {
|
|||
Tags: tags,
|
||||
})
|
||||
}
|
||||
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
func workaroundGetContainerBlob(ctx *context.Context, opts *container_model.BlobSearchOptions) (*packages_model.PackageFileDescriptor, error) {
|
||||
blob, err := container_model.GetContainerBlob(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = packages_module.NewContentStore().Has(packages_module.BlobHash256Key(blob.Blob.HashSHA256))
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
log.Debug("Package registry inconsistent: blob %s does not exist on file system", blob.Blob.HashSHA256)
|
||||
return nil, container_model.ErrContainerBlobNotExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blob, nil
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ package container
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
@ -403,6 +405,15 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack
|
|||
log.Error("Error inserting package blob: %v", err)
|
||||
return nil, false, "", err
|
||||
}
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
// https://github.com/go-gitea/gitea/issues/19586
|
||||
if exists {
|
||||
err = packages_module.NewContentStore().Has(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
|
||||
exists = false
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
contentStore := packages_module.NewContentStore()
|
||||
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), buf, buf.Size()); err != nil {
|
||||
|
|
|
@ -67,6 +67,7 @@ func createPackageMetadataVersion(registryURL string, pd *packages_model.Package
|
|||
PeerDependencies: metadata.PeerDependencies,
|
||||
OptionalDependencies: metadata.OptionalDependencies,
|
||||
Readme: metadata.Readme,
|
||||
Bin: metadata.Bin,
|
||||
Dist: npm_module.PackageDistribution{
|
||||
Shasum: pd.Files[0].Blob.HashSHA1,
|
||||
Integrity: "sha512-" + base64.StdEncoding.EncodeToString(hashBytes),
|
||||
|
|
|
@ -105,6 +105,49 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
ctx.ServeStream(s, pf.Name)
|
||||
}
|
||||
|
||||
// DownloadPackageFileByName finds the version and serves the contents of a package
|
||||
func DownloadPackageFileByName(ctx *context.Context) {
|
||||
filename := ctx.Params("filename")
|
||||
|
||||
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
OwnerID: ctx.Package.Owner.ID,
|
||||
Type: packages_model.TypeNpm,
|
||||
Name: packages_model.SearchValue{
|
||||
ExactMatch: true,
|
||||
Value: packageNameFromParams(ctx),
|
||||
},
|
||||
HasFileWithName: filename,
|
||||
IsInternal: false,
|
||||
})
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if len(pvs) != 1 {
|
||||
apiError(ctx, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
s, pf, err := packages_service.GetFileStreamByPackageVersion(
|
||||
ctx,
|
||||
pvs[0],
|
||||
&packages_service.PackageFileInfo{
|
||||
Filename: filename,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageFileNotExist {
|
||||
apiError(ctx, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ctx.ServeStream(s, pf.Name)
|
||||
}
|
||||
|
||||
// UploadPackage creates a new package
|
||||
func UploadPackage(ctx *context.Context) {
|
||||
npmPackage, err := npm_module.ParsePackage(ctx.Req.Body)
|
||||
|
|
|
@ -5,15 +5,11 @@
|
|||
package nuget
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
// ServiceIndexResponse https://docs.microsoft.com/en-us/nuget/api/service-index#resources
|
||||
|
@ -113,8 +109,8 @@ func createRegistrationIndexResponse(l *linkBuilder, pds []*packages_model.Packa
|
|||
{
|
||||
RegistrationPageURL: l.GetRegistrationIndexURL(pds[0].Package.Name),
|
||||
Count: len(pds),
|
||||
Lower: normalizeVersion(pds[0].SemVer),
|
||||
Upper: normalizeVersion(pds[len(pds)-1].SemVer),
|
||||
Lower: pds[0].Version.Version,
|
||||
Upper: pds[len(pds)-1].Version.Version,
|
||||
Items: items,
|
||||
},
|
||||
},
|
||||
|
@ -191,7 +187,7 @@ type PackageVersionsResponse struct {
|
|||
func createPackageVersionsResponse(pds []*packages_model.PackageDescriptor) *PackageVersionsResponse {
|
||||
versions := make([]string, 0, len(pds))
|
||||
for _, pd := range pds {
|
||||
versions = append(versions, normalizeVersion(pd.SemVer))
|
||||
versions = append(versions, pd.Version.Version)
|
||||
}
|
||||
|
||||
return &PackageVersionsResponse{
|
||||
|
@ -224,20 +220,13 @@ type SearchResultVersion struct {
|
|||
}
|
||||
|
||||
func createSearchResultResponse(l *linkBuilder, totalHits int64, pds []*packages_model.PackageDescriptor) *SearchResultResponse {
|
||||
grouped := make(map[string][]*packages_model.PackageDescriptor)
|
||||
for _, pd := range pds {
|
||||
grouped[pd.Package.Name] = append(grouped[pd.Package.Name], pd)
|
||||
}
|
||||
|
||||
data := make([]*SearchResult, 0, len(pds))
|
||||
|
||||
if len(pds) > 0 {
|
||||
groupID := pds[0].Package.Name
|
||||
group := make([]*packages_model.PackageDescriptor, 0, 10)
|
||||
|
||||
for i := 0; i < len(pds); i++ {
|
||||
if groupID != pds[i].Package.Name {
|
||||
data = append(data, createSearchResult(l, group))
|
||||
groupID = pds[i].Package.Name
|
||||
group = group[:0]
|
||||
}
|
||||
group = append(group, pds[i])
|
||||
}
|
||||
for _, group := range grouped {
|
||||
data = append(data, createSearchResult(l, group))
|
||||
}
|
||||
|
||||
|
@ -273,15 +262,3 @@ func createSearchResult(l *linkBuilder, pds []*packages_model.PackageDescriptor)
|
|||
RegistrationIndexURL: l.GetRegistrationIndexURL(latest.Package.Name),
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeVersion removes the metadata
|
||||
func normalizeVersion(v *version.Version) string {
|
||||
var buf bytes.Buffer
|
||||
segments := v.Segments64()
|
||||
fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
|
||||
pre := v.Prerelease()
|
||||
if pre != "" {
|
||||
fmt.Fprintf(&buf, "-%s", pre)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
|
@ -351,7 +351,7 @@ func processUploadedFile(ctx *context.Context, expectedType nuget_module.Package
|
|||
// DownloadSymbolFile https://github.com/dotnet/symstore/blob/main/docs/specs/Simple_Symbol_Query_Protocol.md#request
|
||||
func DownloadSymbolFile(ctx *context.Context) {
|
||||
filename := ctx.Params("filename")
|
||||
guid := ctx.Params("guid")
|
||||
guid := ctx.Params("guid")[:32]
|
||||
filename2 := ctx.Params("filename2")
|
||||
|
||||
if filename != filename2 {
|
||||
|
|
|
@ -22,12 +22,19 @@ import (
|
|||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
)
|
||||
|
||||
// https://www.python.org/dev/peps/pep-0503/#normalized-names
|
||||
// https://peps.python.org/pep-0426/#name
|
||||
var normalizer = strings.NewReplacer(".", "-", "_", "-")
|
||||
var nameMatcher = regexp.MustCompile(`\A[a-zA-Z0-9\.\-_]+\z`)
|
||||
var nameMatcher = regexp.MustCompile(`\A(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\.\-_]*[a-zA-Z0-9])\z`)
|
||||
|
||||
// https://www.python.org/dev/peps/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
||||
var versionMatcher = regexp.MustCompile(`^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$`)
|
||||
// https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
||||
var versionMatcher = regexp.MustCompile(`\Av?` +
|
||||
`(?:[0-9]+!)?` + // epoch
|
||||
`[0-9]+(?:\.[0-9]+)*` + // release segment
|
||||
`(?:[-_\.]?(?:a|b|c|rc|alpha|beta|pre|preview)[-_\.]?[0-9]*)?` + // pre-release
|
||||
`(?:-[0-9]+|[-_\.]?(?:post|rev|r)[-_\.]?[0-9]*)?` + // post release
|
||||
`(?:[-_\.]?dev[-_\.]?[0-9]*)?` + // dev release
|
||||
`(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)?` + // local version
|
||||
`\z`)
|
||||
|
||||
func apiError(ctx *context.Context, status int, obj interface{}) {
|
||||
helper.LogAndProcessError(ctx, status, obj, func(message string) {
|
||||
|
@ -123,7 +130,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||
|
||||
packageName := normalizer.Replace(ctx.Req.FormValue("name"))
|
||||
packageVersion := ctx.Req.FormValue("version")
|
||||
if !nameMatcher.MatchString(packageName) || !versionMatcher.MatchString(packageVersion) {
|
||||
if !isValidNameAndVersion(packageName, packageVersion) {
|
||||
apiError(ctx, http.StatusBadRequest, "invalid name or version")
|
||||
return
|
||||
}
|
||||
|
@ -141,7 +148,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||
Name: packageName,
|
||||
Version: packageVersion,
|
||||
},
|
||||
SemverCompatible: true,
|
||||
SemverCompatible: false,
|
||||
Creator: ctx.Doer,
|
||||
Metadata: &pypi_module.Metadata{
|
||||
Author: ctx.Req.FormValue("author"),
|
||||
|
@ -172,3 +179,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||
|
||||
ctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
func isValidNameAndVersion(packageName, packageVersion string) bool {
|
||||
return nameMatcher.MatchString(packageName) && versionMatcher.MatchString(packageVersion)
|
||||
}
|
||||
|
|
39
routers/api/packages/pypi/pypi_test.go
Normal file
39
routers/api/packages/pypi/pypi_test.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package pypi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsValidNameAndVersion(t *testing.T) {
|
||||
// The test cases below were created from the following Python PEPs:
|
||||
// https://peps.python.org/pep-0426/#name
|
||||
// https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
||||
|
||||
// Valid Cases
|
||||
assert.True(t, isValidNameAndVersion("A", "1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("Test.Name.1234", "1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("test_name", "1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "v1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "2012.4"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1.0.1-alpha"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1.0.1a1"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1.0b2.r345.dev456"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1!1.0.1"))
|
||||
assert.True(t, isValidNameAndVersion("test-name", "1.0.1+local.1"))
|
||||
|
||||
// Invalid Cases
|
||||
assert.False(t, isValidNameAndVersion(".test-name", "1.0.1"))
|
||||
assert.False(t, isValidNameAndVersion("test!name", "1.0.1"))
|
||||
assert.False(t, isValidNameAndVersion("-test-name", "1.0.1"))
|
||||
assert.False(t, isValidNameAndVersion("test-name-", "1.0.1"))
|
||||
assert.False(t, isValidNameAndVersion("test-name", "a1.0.1"))
|
||||
assert.False(t, isValidNameAndVersion("test-name", "1.0.1aa"))
|
||||
assert.False(t, isValidNameAndVersion("test-name", "1.0.0-alpha.beta"))
|
||||
}
|
|
@ -75,7 +75,9 @@ func enumeratePackages(ctx *context.Context, filename string, pvs []*packages_mo
|
|||
})
|
||||
}
|
||||
|
||||
ctx.SetServeHeaders(filename + ".gz")
|
||||
ctx.SetServeHeaders(&context.ServeHeaderOptions{
|
||||
Filename: filename + ".gz",
|
||||
})
|
||||
|
||||
zw := gzip.NewWriter(ctx.Resp)
|
||||
defer zw.Close()
|
||||
|
@ -113,7 +115,9 @@ func ServePackageSpecification(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
ctx.SetServeHeaders(filename)
|
||||
ctx.SetServeHeaders(&context.ServeHeaderOptions{
|
||||
Filename: filename,
|
||||
})
|
||||
|
||||
zw := zlib.NewWriter(ctx.Resp)
|
||||
defer zw.Close()
|
||||
|
|
|
@ -890,7 +890,7 @@ func Routes() *web.Route {
|
|||
m.Group("/{index}", func() {
|
||||
m.Combo("").Get(repo.GetIssue).
|
||||
Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue).
|
||||
Delete(reqToken(), reqAdmin(), repo.DeleteIssue)
|
||||
Delete(reqToken(), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue)
|
||||
m.Group("/comments", func() {
|
||||
m.Combo("").Get(repo.ListIssueComments).
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
|
||||
|
|
|
@ -252,42 +252,50 @@ func ListBranches(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/BranchList"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
skip, _ := listOptions.GetStartEnd()
|
||||
branches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
|
||||
return
|
||||
}
|
||||
var totalNumOfBranches int
|
||||
var apiBranches []*api.Branch
|
||||
|
||||
apiBranches := make([]*api.Branch, 0, len(branches))
|
||||
for i := range branches {
|
||||
c, err := branches[i].GetCommit()
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil {
|
||||
skip, _ := listOptions.GetStartEnd()
|
||||
branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
|
||||
if err != nil {
|
||||
// Skip if this branch doesn't exist anymore.
|
||||
if git.IsErrNotExist(err) {
|
||||
totalNumOfBranches--
|
||||
continue
|
||||
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiBranches = make([]*api.Branch, 0, len(branches))
|
||||
for i := range branches {
|
||||
c, err := branches[i].GetCommit()
|
||||
if err != nil {
|
||||
// Skip if this branch doesn't exist anymore.
|
||||
if git.IsErrNotExist(err) {
|
||||
total--
|
||||
continue
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
||||
return
|
||||
branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branches[i].Name)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
|
||||
return
|
||||
}
|
||||
apiBranch, err := convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
|
||||
return
|
||||
}
|
||||
apiBranches = append(apiBranches, apiBranch)
|
||||
}
|
||||
branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branches[i].Name)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
|
||||
return
|
||||
}
|
||||
apiBranch, err := convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
|
||||
return
|
||||
}
|
||||
apiBranches = append(apiBranches, apiBranch)
|
||||
|
||||
totalNumOfBranches = total
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(int64(totalNumOfBranches))
|
||||
ctx.JSON(http.StatusOK, &apiBranches)
|
||||
ctx.JSON(http.StatusOK, apiBranches)
|
||||
}
|
||||
|
||||
// GetBranchProtection gets a branch protection
|
||||
|
|
|
@ -169,15 +169,16 @@ func TestHook(ctx *context.APIContext) {
|
|||
|
||||
commitID := ctx.Repo.Commit.ID.String()
|
||||
if err := webhook_service.PrepareWebhook(hook, ctx.Repo.Repository, webhook.HookEventPush, &api.PushPayload{
|
||||
Ref: ref,
|
||||
Before: commitID,
|
||||
After: commitID,
|
||||
CompareURL: setting.AppURL + ctx.Repo.Repository.ComposeCompareURL(commitID, commitID),
|
||||
Commits: []*api.PayloadCommit{commit},
|
||||
HeadCommit: commit,
|
||||
Repo: convert.ToRepo(ctx.Repo.Repository, perm.AccessModeNone),
|
||||
Pusher: convert.ToUserWithAccessMode(ctx.Doer, perm.AccessModeNone),
|
||||
Sender: convert.ToUserWithAccessMode(ctx.Doer, perm.AccessModeNone),
|
||||
Ref: ref,
|
||||
Before: commitID,
|
||||
After: commitID,
|
||||
CompareURL: setting.AppURL + ctx.Repo.Repository.ComposeCompareURL(commitID, commitID),
|
||||
Commits: []*api.PayloadCommit{commit},
|
||||
TotalCommits: 1,
|
||||
HeadCommit: commit,
|
||||
Repo: convert.ToRepo(ctx.Repo.Repository, perm.AccessModeNone),
|
||||
Pusher: convert.ToUserWithAccessMode(ctx.Doer, perm.AccessModeNone),
|
||||
Sender: convert.ToUserWithAccessMode(ctx.Doer, perm.AccessModeNone),
|
||||
}); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "PrepareWebhook: ", err)
|
||||
return
|
||||
|
|
|
@ -7,7 +7,6 @@ package common
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -53,50 +52,44 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read
|
|||
buf = buf[:n]
|
||||
}
|
||||
|
||||
httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 5*time.Minute)
|
||||
|
||||
if size >= 0 {
|
||||
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size))
|
||||
} else {
|
||||
log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size)
|
||||
}
|
||||
|
||||
fileName := path.Base(filePath)
|
||||
sniffedType := typesniffer.DetectContentType(buf)
|
||||
isPlain := sniffedType.IsText() || ctx.FormBool("render")
|
||||
mimeType := ""
|
||||
charset := ""
|
||||
|
||||
if setting.MimeTypeMap.Enabled {
|
||||
fileExtension := strings.ToLower(filepath.Ext(fileName))
|
||||
mimeType = setting.MimeTypeMap.Map[fileExtension]
|
||||
opts := &context.ServeHeaderOptions{
|
||||
Filename: path.Base(filePath),
|
||||
}
|
||||
|
||||
if mimeType == "" {
|
||||
sniffedType := typesniffer.DetectContentType(buf)
|
||||
isPlain := sniffedType.IsText() || ctx.FormBool("render")
|
||||
|
||||
if setting.MimeTypeMap.Enabled {
|
||||
fileExtension := strings.ToLower(filepath.Ext(filePath))
|
||||
opts.ContentType = setting.MimeTypeMap.Map[fileExtension]
|
||||
}
|
||||
|
||||
if opts.ContentType == "" {
|
||||
if sniffedType.IsBrowsableBinaryType() {
|
||||
mimeType = sniffedType.GetMimeType()
|
||||
opts.ContentType = sniffedType.GetMimeType()
|
||||
} else if isPlain {
|
||||
mimeType = "text/plain"
|
||||
opts.ContentType = "text/plain"
|
||||
} else {
|
||||
mimeType = typesniffer.ApplicationOctetStream
|
||||
opts.ContentType = typesniffer.ApplicationOctetStream
|
||||
}
|
||||
}
|
||||
|
||||
if isPlain {
|
||||
var charset string
|
||||
charset, err = charsetModule.DetectEncoding(buf)
|
||||
if err != nil {
|
||||
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err)
|
||||
charset = "utf-8"
|
||||
}
|
||||
opts.ContentTypeCharset = strings.ToLower(charset)
|
||||
}
|
||||
|
||||
if charset != "" {
|
||||
ctx.Resp.Header().Set("Content-Type", mimeType+"; charset="+strings.ToLower(charset))
|
||||
} else {
|
||||
ctx.Resp.Header().Set("Content-Type", mimeType)
|
||||
}
|
||||
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
|
||||
isSVG := sniffedType.IsSvgImage()
|
||||
|
||||
// serve types that can present a security risk with CSP
|
||||
|
@ -109,16 +102,12 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read
|
|||
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
|
||||
}
|
||||
|
||||
disposition := "inline"
|
||||
opts.Disposition = "inline"
|
||||
if isSVG && !setting.UI.SVG.Enabled {
|
||||
disposition = "attachment"
|
||||
opts.Disposition = "attachment"
|
||||
}
|
||||
|
||||
// encode filename per https://datatracker.ietf.org/doc/html/rfc5987
|
||||
encodedFileName := `filename*=UTF-8''` + url.PathEscape(fileName)
|
||||
|
||||
ctx.Resp.Header().Set("Content-Disposition", disposition+"; "+encodedFileName)
|
||||
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
|
||||
ctx.SetServeHeaders(opts)
|
||||
|
||||
_, err = ctx.Resp.Write(buf)
|
||||
if err != nil {
|
||||
|
|
|
@ -74,21 +74,31 @@ func InitGitServices() {
|
|||
mustInit(repo_service.Init)
|
||||
}
|
||||
|
||||
func syncAppPathForGit(ctx context.Context) error {
|
||||
func syncAppConfForGit(ctx context.Context) error {
|
||||
runtimeState := new(appstate.RuntimeState)
|
||||
if err := appstate.AppState.Get(runtimeState); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updated := false
|
||||
if runtimeState.LastAppPath != setting.AppPath {
|
||||
log.Info("AppPath changed from '%s' to '%s'", runtimeState.LastAppPath, setting.AppPath)
|
||||
runtimeState.LastAppPath = setting.AppPath
|
||||
updated = true
|
||||
}
|
||||
if runtimeState.LastCustomConf != setting.CustomConf {
|
||||
log.Info("CustomConf changed from '%s' to '%s'", runtimeState.LastCustomConf, setting.CustomConf)
|
||||
runtimeState.LastCustomConf = setting.CustomConf
|
||||
updated = true
|
||||
}
|
||||
|
||||
if updated {
|
||||
log.Info("re-sync repository hooks ...")
|
||||
mustInitCtx(ctx, repo_service.SyncRepositoryHooks)
|
||||
|
||||
log.Info("re-write ssh public keys ...")
|
||||
mustInit(asymkey_model.RewriteAllPublicKeys)
|
||||
|
||||
runtimeState.LastAppPath = setting.AppPath
|
||||
return appstate.AppState.Set(runtimeState)
|
||||
}
|
||||
return nil
|
||||
|
@ -153,7 +163,7 @@ func GlobalInitInstalled(ctx context.Context) {
|
|||
mustInit(repo_migrations.Init)
|
||||
eventsource.GetManager().Init()
|
||||
|
||||
mustInitCtx(ctx, syncAppPathForGit)
|
||||
mustInitCtx(ctx, syncAppConfForGit)
|
||||
|
||||
mustInit(ssh.Init)
|
||||
|
||||
|
|
|
@ -613,7 +613,9 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
|
|||
// update external user information
|
||||
if gothUser != nil {
|
||||
if err := externalaccount.UpdateExternalUser(u, *gothUser); err != nil {
|
||||
log.Error("UpdateExternalUser failed: %v", err)
|
||||
if !user_model.IsErrExternalLoginUserNotExist(err) {
|
||||
log.Error("UpdateExternalUser failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -773,6 +775,13 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
|
|||
return
|
||||
}
|
||||
|
||||
// Register last login
|
||||
user.SetLastLogin()
|
||||
if err := user_model.UpdateUserCols(ctx, user, "last_login_unix"); err != nil {
|
||||
ctx.ServerError("UpdateUserCols", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("auth.account_activated"))
|
||||
ctx.Redirect(setting.AppSubURL + "/")
|
||||
}
|
||||
|
|
|
@ -1061,7 +1061,9 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
|
|||
|
||||
// update external user information
|
||||
if err := externalaccount.UpdateExternalUser(u, gothUser); err != nil {
|
||||
log.Error("UpdateExternalUser failed: %v", err)
|
||||
if !user_model.IsErrExternalLoginUserNotExist(err) {
|
||||
log.Error("UpdateExternalUser failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := resetLocale(ctx, u); err != nil {
|
||||
|
|
|
@ -58,6 +58,10 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,
|
|||
orderBy = "`user`.updated_unix ASC"
|
||||
case "reversealphabetically":
|
||||
orderBy = "`user`.name DESC"
|
||||
case "lastlogin":
|
||||
orderBy = "`user`.last_login_unix ASC"
|
||||
case "reverselastlogin":
|
||||
orderBy = "`user`.last_login_unix DESC"
|
||||
case UserSearchDefaultSortType: // "alphabetically"
|
||||
default:
|
||||
orderBy = "`user`.name ASC"
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package feed
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
|
@ -57,7 +56,6 @@ func showUserFeed(ctx *context.Context, formatType string) {
|
|||
|
||||
// writeFeed write a feeds.Feed as atom or rss to ctx.Resp
|
||||
func writeFeed(ctx *context.Context, feed *feeds.Feed, formatType string) {
|
||||
ctx.Resp.WriteHeader(http.StatusOK)
|
||||
if formatType == "atom" {
|
||||
ctx.Resp.Header().Set("Content-Type", "application/atom+xml;charset=utf-8")
|
||||
if err := feed.WriteAtom(ctx.Resp); err != nil {
|
||||
|
|
|
@ -1273,15 +1273,16 @@ func TestWebhook(ctx *context.Context) {
|
|||
|
||||
commitID := commit.ID.String()
|
||||
p := &api.PushPayload{
|
||||
Ref: git.BranchPrefix + ctx.Repo.Repository.DefaultBranch,
|
||||
Before: commitID,
|
||||
After: commitID,
|
||||
CompareURL: setting.AppURL + ctx.Repo.Repository.ComposeCompareURL(commitID, commitID),
|
||||
Commits: []*api.PayloadCommit{apiCommit},
|
||||
HeadCommit: apiCommit,
|
||||
Repo: convert.ToRepo(ctx.Repo.Repository, perm.AccessModeNone),
|
||||
Pusher: apiUser,
|
||||
Sender: apiUser,
|
||||
Ref: git.BranchPrefix + ctx.Repo.Repository.DefaultBranch,
|
||||
Before: commitID,
|
||||
After: commitID,
|
||||
CompareURL: setting.AppURL + ctx.Repo.Repository.ComposeCompareURL(commitID, commitID),
|
||||
Commits: []*api.PayloadCommit{apiCommit},
|
||||
TotalCommits: 1,
|
||||
HeadCommit: apiCommit,
|
||||
Repo: convert.ToRepo(ctx.Repo.Repository, perm.AccessModeNone),
|
||||
Pusher: apiUser,
|
||||
Sender: apiUser,
|
||||
}
|
||||
if err := webhook_service.PrepareWebhook(w, ctx.Repo.Repository, webhook.HookEventPush, p); err != nil {
|
||||
ctx.Flash.Error("PrepareWebhook: " + err.Error())
|
||||
|
|
|
@ -31,6 +31,10 @@ func AvatarByUserName(ctx *context.Context) {
|
|||
if strings.ToLower(userName) != "ghost" {
|
||||
var err error
|
||||
if user, err = user_model.GetUserByName(ctx, userName); err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.NotFound("GetUserByName", err)
|
||||
return
|
||||
}
|
||||
ctx.ServerError("Invalid user: "+userName, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -566,6 +566,8 @@ func RegisterRoutes(m *web.Route) {
|
|||
m.Post("/delete", admin.DeleteNotices)
|
||||
m.Post("/empty", admin.EmptyNotices)
|
||||
})
|
||||
}, func(ctx *context.Context) {
|
||||
ctx.Data["EnablePackages"] = setting.Packages.Enabled
|
||||
}, adminReq)
|
||||
// ***** END: Admin *****
|
||||
|
||||
|
@ -597,7 +599,6 @@ func RegisterRoutes(m *web.Route) {
|
|||
reqRepoReleaseWriter := context.RequireRepoWriter(unit.TypeReleases)
|
||||
reqRepoReleaseReader := context.RequireRepoReader(unit.TypeReleases)
|
||||
reqRepoWikiWriter := context.RequireRepoWriter(unit.TypeWiki)
|
||||
reqRepoIssueWriter := context.RequireRepoWriter(unit.TypeIssues)
|
||||
reqRepoIssueReader := context.RequireRepoReader(unit.TypeIssues)
|
||||
reqRepoPullsReader := context.RequireRepoReader(unit.TypePullRequests)
|
||||
reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(unit.TypeIssues, unit.TypePullRequests)
|
||||
|
@ -891,8 +892,8 @@ func RegisterRoutes(m *web.Route) {
|
|||
})
|
||||
})
|
||||
m.Post("/reactions/{action}", bindIgnErr(forms.ReactionForm{}), repo.ChangeIssueReaction)
|
||||
m.Post("/lock", reqRepoIssueWriter, bindIgnErr(forms.IssueLockForm{}), repo.LockIssue)
|
||||
m.Post("/unlock", reqRepoIssueWriter, repo.UnlockIssue)
|
||||
m.Post("/lock", reqRepoIssuesOrPullsWriter, bindIgnErr(forms.IssueLockForm{}), repo.LockIssue)
|
||||
m.Post("/unlock", reqRepoIssuesOrPullsWriter, repo.UnlockIssue)
|
||||
m.Post("/delete", reqRepoAdmin, repo.DeleteIssue)
|
||||
}, context.RepoMustNotBeArchived())
|
||||
m.Group("/{index}", func() {
|
||||
|
|
|
@ -39,6 +39,10 @@ func (s *Session) Verify(req *http.Request, w http.ResponseWriter, store DataSto
|
|||
|
||||
// SessionUser returns the user object corresponding to the "uid" session variable.
|
||||
func SessionUser(sess SessionStore) *user_model.User {
|
||||
if sess == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get user ID
|
||||
uid := sess.Get("uid")
|
||||
if uid == nil {
|
||||
|
|
|
@ -1505,8 +1505,13 @@ func SyncAndGetUserSpecificDiff(ctx context.Context, userID int64, pull *issues_
|
|||
}
|
||||
|
||||
changedFiles, err := gitRepo.GetFilesChangedBetween(review.CommitSHA, latestCommit)
|
||||
// There are way too many possible errors.
|
||||
// Examples are various git errors such as the commit the review was based on was gc'ed and hence doesn't exist anymore as well as unrecoverable errors where we should serve a 500 response
|
||||
// Due to the current architecture and physical limitation of needing to compare explicit error messages, we can only choose one approach without the code getting ugly
|
||||
// For SOME of the errors such as the gc'ed commit, it would be best to mark all files as changed
|
||||
// But as that does not work for all potential errors, we simply mark all files as unchanged and drop the error which always works, even if not as good as possible
|
||||
if err != nil {
|
||||
return diff, err
|
||||
log.Error("Could not get changed files between %s and %s for pull request %d in repo with path %s. Assuming no changes. Error: %w", review.CommitSHA, latestCommit, pull.Index, gitRepo.Path, err)
|
||||
}
|
||||
|
||||
filesChangedSinceLastDiff := make(map[string]pull_model.ViewedState)
|
||||
|
|
|
@ -220,9 +220,21 @@ func deleteIssue(issue *issues_model.Issue) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, issue.IsClosed); err != nil {
|
||||
// update the total issue numbers
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// if the issue is closed, update the closed issue numbers
|
||||
if issue.IsClosed {
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := issues_model.UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
|
||||
return fmt.Errorf("error updating counters for milestone id %d: %w",
|
||||
issue.MilestoneID, err)
|
||||
}
|
||||
|
||||
if err := models.DeleteIssueActions(ctx, issue.RepoID, issue.ID); err != nil {
|
||||
return err
|
||||
|
|
|
@ -438,14 +438,21 @@ func buildObjectResponse(rc *requestContext, pointer lfs_module.Pointer, downloa
|
|||
}
|
||||
|
||||
if download {
|
||||
rep.Actions["download"] = &lfs_module.Link{Href: rc.DownloadLink(pointer), Header: header}
|
||||
var link *lfs_module.Link
|
||||
if setting.LFS.ServeDirect {
|
||||
// If we have a signed url (S3, object storage), redirect to this directly.
|
||||
u, err := storage.LFS.URL(pointer.RelativePath(), pointer.Oid)
|
||||
if u != nil && err == nil {
|
||||
rep.Actions["download"] = &lfs_module.Link{Href: u.String(), Header: header}
|
||||
// Presigned url does not need the Authorization header
|
||||
// https://github.com/go-gitea/gitea/issues/21525
|
||||
delete(header, "Authorization")
|
||||
link = &lfs_module.Link{Href: u.String(), Header: header}
|
||||
}
|
||||
}
|
||||
if link == nil {
|
||||
link = &lfs_module.Link{Href: rc.DownloadLink(pointer), Header: header}
|
||||
}
|
||||
rep.Actions["download"] = link
|
||||
}
|
||||
if upload {
|
||||
rep.Actions["upload"] = &lfs_module.Link{Href: rc.UploadLink(pointer), Header: header}
|
||||
|
|
|
@ -52,6 +52,8 @@ var patchErrorSuffices = []string{
|
|||
": patch does not apply",
|
||||
": already exists in working directory",
|
||||
"unrecognized input",
|
||||
": No such file or directory",
|
||||
": does not exist in index",
|
||||
}
|
||||
|
||||
// TestPatch will test whether a simple patch will apply
|
||||
|
@ -415,6 +417,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
|
|||
scanner := bufio.NewScanner(stderrReader)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
log.Trace("PullRequest[%d].testPatch: stderr: %s", pr.ID, line)
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
conflict = true
|
||||
filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0])
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
|
@ -220,21 +221,21 @@ func DeleteUnadoptedRepository(doer, u *user_model.User, repoName string) error
|
|||
return util.RemoveAll(repoPath)
|
||||
}
|
||||
|
||||
type unadoptedRrepositories struct {
|
||||
type unadoptedRepositories struct {
|
||||
repositories []string
|
||||
index int
|
||||
start int
|
||||
end int
|
||||
}
|
||||
|
||||
func (unadopted *unadoptedRrepositories) add(repository string) {
|
||||
func (unadopted *unadoptedRepositories) add(repository string) {
|
||||
if unadopted.index >= unadopted.start && unadopted.index < unadopted.end {
|
||||
unadopted.repositories = append(unadopted.repositories, repository)
|
||||
}
|
||||
unadopted.index++
|
||||
}
|
||||
|
||||
func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unadopted *unadoptedRrepositories) error {
|
||||
func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unadopted *unadoptedRepositories) error {
|
||||
if len(repoNamesToCheck) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -266,7 +267,7 @@ func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unad
|
|||
}
|
||||
for _, repoName := range repoNamesToCheck {
|
||||
if _, ok := repoNames[repoName]; !ok {
|
||||
unadopted.add(filepath.Join(userName, repoName))
|
||||
unadopted.add(path.Join(userName, repoName)) // These are not used as filepaths - but as reponames - therefore use path.Join not filepath.Join
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -294,7 +295,7 @@ func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, in
|
|||
var repoNamesToCheck []string
|
||||
|
||||
start := (opts.Page - 1) * opts.PageSize
|
||||
unadopted := &unadoptedRrepositories{
|
||||
unadopted := &unadoptedRepositories{
|
||||
repositories: make([]string, 0, opts.PageSize),
|
||||
start: start,
|
||||
end: start + opts.PageSize,
|
||||
|
@ -339,7 +340,7 @@ func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, in
|
|||
}
|
||||
|
||||
repoNamesToCheck = append(repoNamesToCheck, name)
|
||||
if len(repoNamesToCheck) > setting.Database.IterateBufferSize {
|
||||
if len(repoNamesToCheck) >= setting.Database.IterateBufferSize {
|
||||
if err = checkUnadoptedRepositories(userName, repoNamesToCheck, unadopted); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
func TestCheckUnadoptedRepositories_Add(t *testing.T) {
|
||||
start := 10
|
||||
end := 20
|
||||
unadopted := &unadoptedRrepositories{
|
||||
unadopted := &unadoptedRepositories{
|
||||
start: start,
|
||||
end: end,
|
||||
index: 0,
|
||||
|
@ -39,7 +39,7 @@ func TestCheckUnadoptedRepositories(t *testing.T) {
|
|||
//
|
||||
// Non existent user
|
||||
//
|
||||
unadopted := &unadoptedRrepositories{start: 0, end: 100}
|
||||
unadopted := &unadoptedRepositories{start: 0, end: 100}
|
||||
err := checkUnadoptedRepositories("notauser", []string{"repo"}, unadopted)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(unadopted.repositories))
|
||||
|
@ -50,14 +50,14 @@ func TestCheckUnadoptedRepositories(t *testing.T) {
|
|||
userName := "user2"
|
||||
repoName := "repo2"
|
||||
unadoptedRepoName := "unadopted"
|
||||
unadopted = &unadoptedRrepositories{start: 0, end: 100}
|
||||
unadopted = &unadoptedRepositories{start: 0, end: 100}
|
||||
err = checkUnadoptedRepositories(userName, []string{repoName, unadoptedRepoName}, unadopted)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{path.Join(userName, unadoptedRepoName)}, unadopted.repositories)
|
||||
//
|
||||
// Existing (adopted) repository is not returned
|
||||
//
|
||||
unadopted = &unadoptedRrepositories{start: 0, end: 100}
|
||||
unadopted = &unadoptedRepositories{start: 0, end: 100}
|
||||
err = checkUnadoptedRepositories(userName, []string{repoName}, unadopted)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(unadopted.repositories))
|
||||
|
|
|
@ -220,10 +220,6 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||
log.Error("updateIssuesCommit: %v", err)
|
||||
}
|
||||
|
||||
if len(commits.Commits) > setting.UI.FeedMaxCommitNum {
|
||||
commits.Commits = commits.Commits[:setting.UI.FeedMaxCommitNum]
|
||||
}
|
||||
|
||||
oldCommitID := opts.OldCommitID
|
||||
if oldCommitID == git.EmptySHA && len(commits.Commits) > 0 {
|
||||
oldCommit, err := gitRepo.GetCommit(commits.Commits[len(commits.Commits)-1].Sha1)
|
||||
|
@ -251,6 +247,10 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||
commits.CompareURL = ""
|
||||
}
|
||||
|
||||
if len(commits.Commits) > setting.UI.FeedMaxCommitNum {
|
||||
commits.Commits = commits.Commits[:setting.UI.FeedMaxCommitNum]
|
||||
}
|
||||
|
||||
notification.NotifyPushCommits(pusher, repo, opts, commits)
|
||||
|
||||
if err = git_model.RemoveDeletedBranchByName(repo.ID, branch); err != nil {
|
||||
|
|
|
@ -67,14 +67,14 @@ func (d *DingtalkPayload) Push(p *api.PushPayload) (api.Payloader, error) {
|
|||
)
|
||||
|
||||
var titleLink, linkText string
|
||||
if len(p.Commits) == 1 {
|
||||
if p.TotalCommits == 1 {
|
||||
commitDesc = "1 new commit"
|
||||
titleLink = p.Commits[0].URL
|
||||
linkText = fmt.Sprintf("view commit %s", p.Commits[0].ID[:7])
|
||||
linkText = "view commit"
|
||||
} else {
|
||||
commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
|
||||
commitDesc = fmt.Sprintf("%d new commits", p.TotalCommits)
|
||||
titleLink = p.CompareURL
|
||||
linkText = fmt.Sprintf("view commit %s...%s", p.Commits[0].ID[:7], p.Commits[len(p.Commits)-1].ID[:7])
|
||||
linkText = "view commits"
|
||||
}
|
||||
if titleLink == "" {
|
||||
titleLink = p.Repo.HTMLURL + "/src/" + util.PathEscapeSegments(branchName)
|
||||
|
|
|
@ -82,7 +82,7 @@ func TestDingTalkPayload(t *testing.T) {
|
|||
|
||||
assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.(*DingtalkPayload).ActionCard.Text)
|
||||
assert.Equal(t, "[test/repo:test] 2 new commits", pl.(*DingtalkPayload).ActionCard.Title)
|
||||
assert.Equal(t, "view commit 2020558...2020558", pl.(*DingtalkPayload).ActionCard.SingleTitle)
|
||||
assert.Equal(t, "view commits", pl.(*DingtalkPayload).ActionCard.SingleTitle)
|
||||
assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
|
||||
})
|
||||
|
||||
|
|
|
@ -141,11 +141,11 @@ func (d *DiscordPayload) Push(p *api.PushPayload) (api.Payloader, error) {
|
|||
)
|
||||
|
||||
var titleLink string
|
||||
if len(p.Commits) == 1 {
|
||||
if p.TotalCommits == 1 {
|
||||
commitDesc = "1 new commit"
|
||||
titleLink = p.Commits[0].URL
|
||||
} else {
|
||||
commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
|
||||
commitDesc = fmt.Sprintf("%d new commits", p.TotalCommits)
|
||||
titleLink = p.CompareURL
|
||||
}
|
||||
if titleLink == "" {
|
||||
|
|
|
@ -82,12 +82,13 @@ func pushTestPayload() *api.PushPayload {
|
|||
}
|
||||
|
||||
return &api.PushPayload{
|
||||
Ref: "refs/heads/test",
|
||||
Before: "2020558fe2e34debb818a514715839cabd25e777",
|
||||
After: "2020558fe2e34debb818a514715839cabd25e778",
|
||||
CompareURL: "",
|
||||
HeadCommit: commit,
|
||||
Commits: []*api.PayloadCommit{commit, commit},
|
||||
Ref: "refs/heads/test",
|
||||
Before: "2020558fe2e34debb818a514715839cabd25e777",
|
||||
After: "2020558fe2e34debb818a514715839cabd25e778",
|
||||
CompareURL: "",
|
||||
HeadCommit: commit,
|
||||
Commits: []*api.PayloadCommit{commit, commit},
|
||||
TotalCommits: 2,
|
||||
Repo: &api.Repository{
|
||||
HTMLURL: "http://localhost:3000/test/repo",
|
||||
Name: "repo",
|
||||
|
|
|
@ -154,10 +154,10 @@ func (m *MatrixPayloadUnsafe) Release(p *api.ReleasePayload) (api.Payloader, err
|
|||
func (m *MatrixPayloadUnsafe) Push(p *api.PushPayload) (api.Payloader, error) {
|
||||
var commitDesc string
|
||||
|
||||
if len(p.Commits) == 1 {
|
||||
if p.TotalCommits == 1 {
|
||||
commitDesc = "1 commit"
|
||||
} else {
|
||||
commitDesc = fmt.Sprintf("%d commits", len(p.Commits))
|
||||
commitDesc = fmt.Sprintf("%d commits", p.TotalCommits)
|
||||
}
|
||||
|
||||
repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
|
||||
|
|
|
@ -124,11 +124,11 @@ func (m *MSTeamsPayload) Push(p *api.PushPayload) (api.Payloader, error) {
|
|||
)
|
||||
|
||||
var titleLink string
|
||||
if len(p.Commits) == 1 {
|
||||
if p.TotalCommits == 1 {
|
||||
commitDesc = "1 new commit"
|
||||
titleLink = p.Commits[0].URL
|
||||
} else {
|
||||
commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
|
||||
commitDesc = fmt.Sprintf("%d new commits", p.TotalCommits)
|
||||
titleLink = p.CompareURL
|
||||
}
|
||||
if titleLink == "" {
|
||||
|
@ -155,7 +155,7 @@ func (m *MSTeamsPayload) Push(p *api.PushPayload) (api.Payloader, error) {
|
|||
text,
|
||||
titleLink,
|
||||
greenColor,
|
||||
&MSTeamsFact{"Commit count:", fmt.Sprintf("%d", len(p.Commits))},
|
||||
&MSTeamsFact{"Commit count:", fmt.Sprintf("%d", p.TotalCommits)},
|
||||
), nil
|
||||
}
|
||||
|
||||
|
|
|
@ -171,10 +171,10 @@ func (s *SlackPayload) Push(p *api.PushPayload) (api.Payloader, error) {
|
|||
commitString string
|
||||
)
|
||||
|
||||
if len(p.Commits) == 1 {
|
||||
if p.TotalCommits == 1 {
|
||||
commitDesc = "1 new commit"
|
||||
} else {
|
||||
commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
|
||||
commitDesc = fmt.Sprintf("%d new commits", p.TotalCommits)
|
||||
}
|
||||
if len(p.CompareURL) > 0 {
|
||||
commitString = SlackLinkFormatter(p.CompareURL, commitDesc)
|
||||
|
|
|
@ -89,11 +89,11 @@ func (t *TelegramPayload) Push(p *api.PushPayload) (api.Payloader, error) {
|
|||
)
|
||||
|
||||
var titleLink string
|
||||
if len(p.Commits) == 1 {
|
||||
if p.TotalCommits == 1 {
|
||||
commitDesc = "1 new commit"
|
||||
titleLink = p.Commits[0].URL
|
||||
} else {
|
||||
commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
|
||||
commitDesc = fmt.Sprintf("%d new commits", p.TotalCommits)
|
||||
titleLink = p.CompareURL
|
||||
}
|
||||
if titleLink == "" {
|
||||
|
|
|
@ -93,7 +93,7 @@ func (f *WechatworkPayload) Push(p *api.PushPayload) (api.Payloader, error) {
|
|||
for i, commit := range p.Commits {
|
||||
var authorName string
|
||||
if commit.Author != nil {
|
||||
authorName = "Author:" + commit.Author.Name
|
||||
authorName = "Author: " + commit.Author.Name
|
||||
}
|
||||
|
||||
message := strings.ReplaceAll(commit.Message, "\n\n", "\r\n")
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
<a class="{{if .PageIsAdminRepositories}}active{{end}} item" href="{{AppSubUrl}}/admin/repos">
|
||||
{{.i18n.Tr "admin.repositories"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsAdminPackages}}active{{end}} item" href="{{AppSubUrl}}/admin/packages">
|
||||
{{.i18n.Tr "packages.title"}}
|
||||
</a>
|
||||
{{if .EnablePackages}}
|
||||
<a class="{{if .PageIsAdminPackages}}active{{end}} item" href="{{AppSubUrl}}/admin/packages">
|
||||
{{.i18n.Tr "packages.title"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if not DisableWebhooks}}
|
||||
<a class="{{if or .PageIsAdminDefaultHooks .PageIsAdminSystemHooks}}active{{end}} item" href="{{AppSubUrl}}/admin/hooks">
|
||||
{{.i18n.Tr "admin.hooks"}}
|
||||
|
|
|
@ -76,9 +76,9 @@
|
|||
<th>{{.i18n.Tr "admin.users.2fa"}}</th>
|
||||
<th>{{.i18n.Tr "admin.users.repos"}}</th>
|
||||
<th>{{.i18n.Tr "admin.users.created"}}</th>
|
||||
<th data-sortt-asc="leastupdate" data-sortt-desc="recentupdate">
|
||||
<th data-sortt-asc="lastlogin" data-sortt-desc="reverselastlogin">
|
||||
{{.i18n.Tr "admin.users.last_login"}}
|
||||
{{SortArrow "leastupdate" "recentupdate" $.SortType false}}
|
||||
{{SortArrow "lastlogin" "reverselastlogin" $.SortType false}}
|
||||
</th>
|
||||
<th>{{.i18n.Tr "admin.users.edit"}}</th>
|
||||
</tr>
|
||||
|
|
|
@ -22,18 +22,18 @@
|
|||
</td>
|
||||
<td class="right aligned overflow-visible">
|
||||
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
|
||||
<div class="ui basic jump button icon tooltip show-create-branch-modal" data-content="{{$.i18n.Tr "repo.branch.new_branch_from" ($.DefaultBranch)}}" data-branch-from="{{$.DefaultBranch}}" data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranch}}" data-modal="#create-branch-modal" data-position="top right">
|
||||
<button class="ui basic jump button icon tooltip show-create-branch-modal" data-content="{{$.i18n.Tr "repo.branch.new_branch_from" ($.DefaultBranch)}}" data-branch-from="{{$.DefaultBranch}}" data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranch}}" data-modal="#create-branch-modal" data-position="top right">
|
||||
{{svg "octicon-git-branch"}}
|
||||
</div>
|
||||
</button>
|
||||
{{end}}
|
||||
{{if not $.DisableDownloadSourceArchives}}
|
||||
<div class="ui basic jump dropdown icon button tooltip" data-content="{{$.i18n.Tr "repo.branch.download" ($.DefaultBranch)}}" data-position="top right">
|
||||
<button class="ui basic jump dropdown icon button tooltip" data-content="{{$.i18n.Tr "repo.branch.download" ($.DefaultBranch)}}" data-position="top right">
|
||||
{{svg "octicon-download"}}
|
||||
<div class="menu">
|
||||
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.zip" rel="nofollow">{{svg "octicon-file-zip"}} ZIP</a>
|
||||
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}} TAR.GZ</a>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -108,26 +108,30 @@
|
|||
</td>
|
||||
<td class="two wide right aligned overflow-visible">
|
||||
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
|
||||
<div class="ui basic jump button icon tooltip show-create-branch-modal" data-branch-from="{{.Name}}" data-branch-from-urlcomponent="{{PathEscapeSegments .Name}}" data-content="{{$.i18n.Tr "repo.branch.new_branch_from" .Name}}" data-position="top right" data-modal="#create-branch-modal" data-name="{{.Name}}">
|
||||
<button class="ui basic jump button icon tooltip show-create-branch-modal" data-branch-from="{{.Name}}" data-branch-from-urlcomponent="{{PathEscapeSegments .Name}}" data-content="{{$.i18n.Tr "repo.branch.new_branch_from" .Name}}" data-position="top right" data-modal="#create-branch-modal" data-name="{{.Name}}">
|
||||
{{svg "octicon-git-branch"}}
|
||||
</div>
|
||||
</button>
|
||||
{{end}}
|
||||
{{if and (not .IsDeleted) (not $.DisableDownloadSourceArchives)}}
|
||||
<div class="ui basic jump dropdown icon button tooltip" data-content="{{$.i18n.Tr "repo.branch.download" (.Name)}}" data-position="top right">
|
||||
<button class="ui basic jump dropdown icon button tooltip" data-content="{{$.i18n.Tr "repo.branch.download" (.Name)}}" data-position="top right">
|
||||
{{svg "octicon-download"}}
|
||||
<div class="menu">
|
||||
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}} ZIP</a>
|
||||
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}} TAR.GZ</a>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
{{end}}
|
||||
{{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}}
|
||||
{{if .IsDeleted}}
|
||||
<a class="ui basic jump button icon tooltip undo-button" href data-url="{{$.Link}}/restore?branch_id={{.DeletedBranch.ID}}&name={{.DeletedBranch.Name}}" data-content="{{$.i18n.Tr "repo.branch.restore" (.Name)}}" data-position="top right"><span class="text blue">{{svg "octicon-reply"}}</span></a>
|
||||
<button class="ui basic jump button icon tooltip undo-button" data-url="{{$.Link}}/restore?branch_id={{.DeletedBranch.ID}}&name={{.DeletedBranch.Name}}" data-content="{{$.i18n.Tr "repo.branch.restore" (.Name)}}" data-position="top right">
|
||||
<span class="text blue">
|
||||
{{svg "octicon-reply"}}
|
||||
</span>
|
||||
</button>
|
||||
{{else}}
|
||||
<a class="ui basic jump button icon tooltip delete-button delete-branch-button" href data-url="{{$.Link}}/delete?name={{.Name}}" data-content="{{$.i18n.Tr "repo.branch.delete" (.Name)}}" data-position="top right" data-name="{{.Name}}">
|
||||
<button class="ui basic jump button icon tooltip delete-button delete-branch-button" data-url="{{$.Link}}/delete?name={{.Name}}" data-content="{{$.i18n.Tr "repo.branch.delete" (.Name)}}" data-position="top right" data-name="{{.Name}}">
|
||||
{{svg "octicon-trash"}}
|
||||
</a>
|
||||
</button>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</td>
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
<!-- If home page, show new PR. If not, show breadcrumb -->
|
||||
{{if eq $n 0}}
|
||||
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
||||
<a href="{{.BaseRepo.Link}}/compare/{{PathEscapeSegments .BaseRepo.DefaultBranch}}...{{if ne .Repository.Owner.Name .BaseRepo.Owner.Name}}{{PathEscape .Repository.Owner.Name}}{{if .BaseRepo.IsFork}}/{{PathEscape .Repository.Name}}{{end}}:{{end}}{{PathEscapeSegments .BranchName}}">
|
||||
<a href="{{CompareLink .BaseRepo .Repository .BranchName}}">
|
||||
<button id="new-pull-request" class="ui compact basic button tooltip" data-content="{{if .PullRequestCtx.Allowed}}{{.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{.i18n.Tr "action.compare_branch"}}{{end}}"><span class="text">{{svg "octicon-git-pull-request"}}</span></button>
|
||||
</a>
|
||||
{{end}}
|
||||
|
|
|
@ -17,6 +17,7 @@ const baseOptions = {
|
|||
rulers: false,
|
||||
scrollbar: {horizontalScrollbarSize: 6, verticalScrollbarSize: 6},
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
};
|
||||
|
||||
function getEditorconfig(input) {
|
||||
|
|
|
@ -35,7 +35,7 @@ export function initHeadNavbarContentToggle() {
|
|||
export function initFootLanguageMenu() {
|
||||
function linkLanguageAction() {
|
||||
const $this = $(this);
|
||||
$.post($this.data('url')).always(() => {
|
||||
$.get($this.data('url')).always(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ import $ from 'jquery';
|
|||
import {svg} from '../svg.js';
|
||||
import {invertFileFolding} from './file-fold.js';
|
||||
|
||||
export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/;
|
||||
export const rangeAnchorRegex = /^#(L[1-9][0-9]*)-(L[1-9][0-9]*)$/;
|
||||
|
||||
function changeHash(hash) {
|
||||
if (window.history.pushState) {
|
||||
window.history.pushState(null, null, hash);
|
||||
|
@ -114,7 +117,7 @@ export function initRepoCodeView() {
|
|||
});
|
||||
|
||||
$(window).on('hashchange', () => {
|
||||
let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/);
|
||||
let m = window.location.hash.match(rangeAnchorRegex);
|
||||
let $list;
|
||||
if ($('div.blame').length) {
|
||||
$list = $('.code-view td.lines-code.blame-code');
|
||||
|
@ -124,27 +127,31 @@ export function initRepoCodeView() {
|
|||
let $first;
|
||||
if (m) {
|
||||
$first = $list.filter(`[rel=${m[1]}]`);
|
||||
selectRange($list, $first, $list.filter(`[rel=${m[2]}]`));
|
||||
if ($first.length) {
|
||||
selectRange($list, $first, $list.filter(`[rel=${m[2]}]`));
|
||||
|
||||
// show code view menu marker (don't show in blame page)
|
||||
if ($('div.blame').length === 0) {
|
||||
showLineButton();
|
||||
// show code view menu marker (don't show in blame page)
|
||||
if ($('div.blame').length === 0) {
|
||||
showLineButton();
|
||||
}
|
||||
|
||||
$('html, body').scrollTop($first.offset().top - 200);
|
||||
return;
|
||||
}
|
||||
|
||||
$('html, body').scrollTop($first.offset().top - 200);
|
||||
return;
|
||||
}
|
||||
m = window.location.hash.match(/^#(L|n)(\d+)$/);
|
||||
m = window.location.hash.match(singleAnchorRegex);
|
||||
if (m) {
|
||||
$first = $list.filter(`[rel=L${m[2]}]`);
|
||||
selectRange($list, $first);
|
||||
if ($first.length) {
|
||||
selectRange($list, $first);
|
||||
|
||||
// show code view menu marker (don't show in blame page)
|
||||
if ($('div.blame').length === 0) {
|
||||
showLineButton();
|
||||
// show code view menu marker (don't show in blame page)
|
||||
if ($('div.blame').length === 0) {
|
||||
showLineButton();
|
||||
}
|
||||
|
||||
$('html, body').scrollTop($first.offset().top - 200);
|
||||
}
|
||||
|
||||
$('html, body').scrollTop($first.offset().top - 200);
|
||||
}
|
||||
}).trigger('hashchange');
|
||||
}
|
||||
|
|
17
web_src/js/features/repo-code.test.js
Normal file
17
web_src/js/features/repo-code.test.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {singleAnchorRegex, rangeAnchorRegex} from './repo-code.js';
|
||||
|
||||
test('singleAnchorRegex', () => {
|
||||
expect(singleAnchorRegex.test('#L0')).toEqual(false);
|
||||
expect(singleAnchorRegex.test('#L1')).toEqual(true);
|
||||
expect(singleAnchorRegex.test('#L01')).toEqual(false);
|
||||
expect(singleAnchorRegex.test('#n0')).toEqual(false);
|
||||
expect(singleAnchorRegex.test('#n1')).toEqual(true);
|
||||
expect(singleAnchorRegex.test('#n01')).toEqual(false);
|
||||
});
|
||||
|
||||
test('rangeAnchorRegex', () => {
|
||||
expect(rangeAnchorRegex.test('#L0-L10')).toEqual(false);
|
||||
expect(rangeAnchorRegex.test('#L1-L10')).toEqual(true);
|
||||
expect(rangeAnchorRegex.test('#L01-L10')).toEqual(false);
|
||||
expect(rangeAnchorRegex.test('#L1-L01')).toEqual(false);
|
||||
});
|
Reference in a new issue