mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-05 18:32:41 +09:00
Compare commits
75 Commits
v1.24.2
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ffd1e04ef | ||
|
|
ac69bf8ab9 | ||
|
|
99053ce4fa | ||
|
|
e818de179e | ||
|
|
0a87bf9016 | ||
|
|
86d99e2f38 | ||
|
|
7bfb7567b2 | ||
|
|
7619808137 | ||
|
|
b854930a96 | ||
|
|
935f5e0ad5 | ||
|
|
08c6ea6728 | ||
|
|
67977f0b1c | ||
|
|
78fbcf35ad | ||
|
|
8f5b1d27d4 | ||
|
|
89c99a4dcb | ||
|
|
3c7e7a19dd | ||
|
|
8313b5d998 | ||
|
|
6ca73bf662 | ||
|
|
5e10def7f7 | ||
|
|
1b8efb6fc7 | ||
|
|
8f89e1e174 | ||
|
|
cbc595b9d9 | ||
|
|
cc5ccf44dc | ||
|
|
f91e35b8b7 | ||
|
|
f52ed422dc | ||
|
|
0266ee5de7 | ||
|
|
ac03e65cf4 | ||
|
|
f3e6672c09 | ||
|
|
136ec9ef81 | ||
|
|
79018ae726 | ||
|
|
e11176192a | ||
|
|
4e0269e890 | ||
|
|
04114c637a | ||
|
|
e5540bfa81 | ||
|
|
d22d6ca0d8 | ||
|
|
d49feab428 | ||
|
|
9162f4403a | ||
|
|
d05cf08fad | ||
|
|
f4b4b0bf98 | ||
|
|
99596044d7 | ||
|
|
693d26914f | ||
|
|
315f197790 | ||
|
|
76b8f0c3a7 | ||
|
|
f99bbd7f3f | ||
|
|
f7ef657b5a | ||
|
|
486d274be6 | ||
|
|
ab3d2a944c | ||
|
|
12bfa9e83d | ||
|
|
dd661e92df | ||
|
|
0b31272c7e | ||
|
|
ec0c418719 | ||
|
|
6dc19fc29a | ||
|
|
9f1baa7d18 | ||
|
|
e13deb7a16 | ||
|
|
e5c1b8b632 | ||
|
|
e931b62f33 | ||
|
|
81ee93e5bc | ||
|
|
053f9186bc | ||
|
|
68fcdb6122 | ||
|
|
14ca309c39 | ||
|
|
4aba42519d | ||
|
|
9adf175df0 | ||
|
|
c3fa2a8729 | ||
|
|
89dfed32e0 | ||
|
|
d5062d0c27 | ||
|
|
90e9e79232 | ||
|
|
c6467edcb1 | ||
|
|
5d5b695527 | ||
|
|
0af7a7b79f | ||
|
|
9339661078 | ||
|
|
1e69f085d6 | ||
|
|
0bfccd8ecf | ||
|
|
534b9b35dd | ||
|
|
dbadc59b56 | ||
|
|
a57e2c4bc3 |
6
.github/workflows/pull-db-tests.yml
vendored
6
.github/workflows/pull-db-tests.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
minio:
|
minio:
|
||||||
# as github actions doesn't support "entrypoint", we need to use a non-official image
|
# as github actions doesn't support "entrypoint", we need to use a non-official image
|
||||||
# that has a custom entrypoint set to "minio server /data"
|
# that has a custom entrypoint set to "minio server /data"
|
||||||
image: bitnami/minio:2023.8.31
|
image: bitnamilegacy/minio:2023.8.31
|
||||||
env:
|
env:
|
||||||
MINIO_ROOT_USER: 123456
|
MINIO_ROOT_USER: 123456
|
||||||
MINIO_ROOT_PASSWORD: 12345678
|
MINIO_ROOT_PASSWORD: 12345678
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- 6379:6379
|
- 6379:6379
|
||||||
minio:
|
minio:
|
||||||
image: bitnami/minio:2021.3.17
|
image: bitnamilegacy/minio:2021.3.17
|
||||||
env:
|
env:
|
||||||
MINIO_ACCESS_KEY: 123456
|
MINIO_ACCESS_KEY: 123456
|
||||||
MINIO_SECRET_KEY: 12345678
|
MINIO_SECRET_KEY: 12345678
|
||||||
@@ -155,7 +155,7 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
# the bitnami mysql image has more options than the official one, it's easier to customize
|
# the bitnami mysql image has more options than the official one, it's easier to customize
|
||||||
image: bitnami/mysql:8.0
|
image: bitnamilegacy/mysql:8.0
|
||||||
env:
|
env:
|
||||||
ALLOW_EMPTY_PASSWORD: true
|
ALLOW_EMPTY_PASSWORD: true
|
||||||
MYSQL_DATABASE: testgitea
|
MYSQL_DATABASE: testgitea
|
||||||
|
|||||||
104
CHANGELOG.md
104
CHANGELOG.md
@@ -4,7 +4,91 @@ This changelog goes through the changes that have been made in each release
|
|||||||
without substantial changes to our git log; to see the highlights of what has
|
without substantial changes to our git log; to see the highlights of what has
|
||||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||||
|
|
||||||
## [1.24.2](https://github.com/go-gitea/gitea/releases/tag/1.24.2) - 2025-06-20
|
## [1.24.7](https://github.com/go-gitea/gitea/releases/tag/1.24.7) - 2025-10-24
|
||||||
|
|
||||||
|
* SECURITY
|
||||||
|
* Refactor legacy code (#35708) (#35713)
|
||||||
|
* Fixing issue #35530: Password Leak in Log Messages (#35584) (#35665)
|
||||||
|
* Fix a bug missed return (#35655) (#35671)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix inputing review comment will remove reviewer (#35591) (#35664)
|
||||||
|
* TESTING
|
||||||
|
* Mock external service in hcaptcha TestCaptcha (#35604) (#35663)
|
||||||
|
* Fix build (#35669)
|
||||||
|
|
||||||
|
## [1.24.6](https://github.com/go-gitea/gitea/releases/tag/1.24.6) - 2025-09-10
|
||||||
|
|
||||||
|
* SECURITY
|
||||||
|
* Upgrade xz to v0.5.15 (#35385)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix a compare page 404 bug when the pull request disabled (#35441) (#35453)
|
||||||
|
* Fix bug when issue disabled, pull request number in the commit message cannot be redirected (#35420) (#35442)
|
||||||
|
* Add author.name field to Swift Package Registry API response (#35410) (#35431)
|
||||||
|
* Remove usernames when empty in discord webhook (#35412) (#35417)
|
||||||
|
* Allow foreachref parser to grow its buffer (#35365) (#35376)
|
||||||
|
* Allow deleting comment with content via API like web did (#35346) (#35354)
|
||||||
|
* Fix atom/rss mixed error (#35345) (#35347)
|
||||||
|
* Fix review request webhook bug (#35339)
|
||||||
|
* Remove duplicate html IDs (#35210) (#35325)
|
||||||
|
* Fix LFS range size header response (#35277) (#35293)
|
||||||
|
* Fix GitHub release assets URL validation (#35287) (#35290)
|
||||||
|
* Fix token lifetime, closes #35230 (#35271) (#35281)
|
||||||
|
* Fix push commits comments when changing the pull request target branch (#35386) (#35443)
|
||||||
|
|
||||||
|
## [1.24.5](https://github.com/go-gitea/gitea/releases/tag/v1.24.5) - 2025-08-12
|
||||||
|
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix a bug where lfs gc never worked. (#35198) (#35255)
|
||||||
|
* Reload issue when sending webhook to make num comments is right. (#35243) (#35248)
|
||||||
|
* Fix bug when review pull request commits (#35192) (#35246)
|
||||||
|
* MISC
|
||||||
|
* Vertically center "Show Resolved" (#35211) (#35218)
|
||||||
|
|
||||||
|
## [1.24.4](https://github.com/go-gitea/gitea/releases/tag/v1.24.4) - 2025-08-03
|
||||||
|
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix various bugs (1.24) (#35186)
|
||||||
|
* Fix migrate input box bug (#35166) (#35171)
|
||||||
|
* Only hide dropzone when no files have been uploaded (#35156) (#35167)
|
||||||
|
* Fix review comment/dimiss comment x reference can be refereced back (#35094) (#35099)
|
||||||
|
* Fix submodule nil check (#35096) (#35098)
|
||||||
|
* MISC
|
||||||
|
* Don't use full-file highlight when there is a git diff textconv (#35114) (#35119)
|
||||||
|
* Increase gap on latest commit (#35104) (#35113)
|
||||||
|
|
||||||
|
## [1.24.3](https://github.com/go-gitea/gitea/releases/tag/v1.24.3) - 2025-07-15
|
||||||
|
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix form property assignment edge case (#35073) (#35078)
|
||||||
|
* Improve submodule relative path handling (#35056) (#35075)
|
||||||
|
* Fix incorrect comment diff hunk parsing, fix github asset ID nil panic (#35046) (#35055)
|
||||||
|
* Fix updating user visibility (#35036) (#35044)
|
||||||
|
* Support base64-encoded agit push options (#35037) (#35041)
|
||||||
|
* Make submodule link work with relative path (#35034) (#35038)
|
||||||
|
* Fix bug when displaying git user avatar in commits list (#35006)
|
||||||
|
* Fix API response for swagger spec (#35029)
|
||||||
|
* Start automerge check again after the conflict check and the schedule (#34988) (#35002)
|
||||||
|
* Fix the response format for actions/workflows (#35009) (#35016)
|
||||||
|
* Fix repo settings and protocol log problems (#35012) (#35013)
|
||||||
|
* Fix project images scroll (#34971) (#34972)
|
||||||
|
* Mark old reviews as stale on agit pr updates (#34933) (#34965)
|
||||||
|
* Fix git graph page (#34948) (#34949)
|
||||||
|
* Don't send trigger for a pending review's comment create/update/delete (#34928) (#34939)
|
||||||
|
* Fix some log and UI problems (#34863) (#34868)
|
||||||
|
* Fix archive API (#34853) (#34857)
|
||||||
|
* Ignore force pushes for changed files in a PR review (#34837) (#34843)
|
||||||
|
* Fix SSH LFS timeout (#34838) (#34842)
|
||||||
|
* Fix team permissions (#34827) (#34836)
|
||||||
|
* Fix job status aggregation logic (#34823) (#34835)
|
||||||
|
* Fix issue filter (#34914) (#34915)
|
||||||
|
* Fix typo in pull request merge warning message text (#34899) (#34903)
|
||||||
|
* Support the open-icon of folder (#34168) (#34896)
|
||||||
|
* Optimize flex layout of release attachment area (#34885) (#34886)
|
||||||
|
* Fix the issue of abnormal interface when there is no issue-item on the project page (#34791) (#34880)
|
||||||
|
* Skip updating timestamp when sync branch (#34875)
|
||||||
|
* Fix required contexts and commit status matching bug (#34815) (#34829)
|
||||||
|
|
||||||
|
## [1.24.2](https://github.com/go-gitea/gitea/releases/tag/v1.24.2) - 2025-06-20
|
||||||
|
|
||||||
* BUGFIXES
|
* BUGFIXES
|
||||||
* Fix container range bug (#34795) (#34796)
|
* Fix container range bug (#34795) (#34796)
|
||||||
@@ -12,7 +96,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
|||||||
* BUILD
|
* BUILD
|
||||||
* Bump poetry feature to new url for dev container (#34787) (#34790)
|
* Bump poetry feature to new url for dev container (#34787) (#34790)
|
||||||
|
|
||||||
## [1.24.1](https://github.com/go-gitea/gitea/releases/tag/1.24.1) - 2025-06-18
|
## [1.24.1](https://github.com/go-gitea/gitea/releases/tag/v1.24.1) - 2025-06-18
|
||||||
|
|
||||||
* ENHANCEMENTS
|
* ENHANCEMENTS
|
||||||
* Improve alignment of commit status icon on commit page (#34750) (#34757)
|
* Improve alignment of commit status icon on commit page (#34750) (#34757)
|
||||||
@@ -32,7 +116,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
|||||||
* Hide href attribute of a tag if there is no target_url (#34556) (#34684)
|
* Hide href attribute of a tag if there is no target_url (#34556) (#34684)
|
||||||
* Fix tag target (#34781) #34783
|
* Fix tag target (#34781) #34783
|
||||||
|
|
||||||
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/1.24.0) - 2025-05-26
|
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/v1.24.0) - 2025-05-26
|
||||||
|
|
||||||
* BREAKING
|
* BREAKING
|
||||||
* Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076)
|
* Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076)
|
||||||
@@ -402,7 +486,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
|||||||
* Bump x/net (#32896) (#32900)
|
* Bump x/net (#32896) (#32900)
|
||||||
* Only activity tab needs heatmap data loading (#34652)
|
* Only activity tab needs heatmap data loading (#34652)
|
||||||
|
|
||||||
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
|
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/v1.23.8) - 2025-05-11
|
||||||
|
|
||||||
* SECURITY
|
* SECURITY
|
||||||
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
|
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
|
||||||
@@ -429,7 +513,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
|||||||
* Bump go version in go.mod (#34160)
|
* Bump go version in go.mod (#34160)
|
||||||
* remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
|
* remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
|
||||||
|
|
||||||
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07
|
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/v1.23.7) - 2025-04-07
|
||||||
|
|
||||||
* Enhancements
|
* Enhancements
|
||||||
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
|
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
|
||||||
@@ -527,7 +611,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
|||||||
* BUGFIXES
|
* BUGFIXES
|
||||||
* Fix a bug caused by status webhook template #33512
|
* Fix a bug caused by status webhook template #33512
|
||||||
|
|
||||||
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/1.23.2) - 2025-02-04
|
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/v1.23.2) - 2025-02-04
|
||||||
|
|
||||||
* BREAKING
|
* BREAKING
|
||||||
* Add tests for webhook and fix some webhook bugs (#33396) (#33442)
|
* Add tests for webhook and fix some webhook bugs (#33396) (#33442)
|
||||||
@@ -3057,7 +3141,7 @@ Key highlights of this release encompass significant changes categorized under `
|
|||||||
* Improve decryption failure message (#24573) (#24575)
|
* Improve decryption failure message (#24573) (#24575)
|
||||||
* Makefile: Use portable !, not GNUish -not, with find(1). (#24565) (#24572)
|
* Makefile: Use portable !, not GNUish -not, with find(1). (#24565) (#24572)
|
||||||
|
|
||||||
## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/1.19.3) - 2023-05-03
|
## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/v1.19.3) - 2023-05-03
|
||||||
|
|
||||||
* SECURITY
|
* SECURITY
|
||||||
* Use golang 1.20.4 to fix CVE-2023-24539, CVE-2023-24540, and CVE-2023-29400
|
* Use golang 1.20.4 to fix CVE-2023-24539, CVE-2023-24540, and CVE-2023-29400
|
||||||
@@ -3070,7 +3154,7 @@ Key highlights of this release encompass significant changes categorized under `
|
|||||||
* Fix incorrect CurrentUser check for docker rootless (#24435)
|
* Fix incorrect CurrentUser check for docker rootless (#24435)
|
||||||
* Getting the tag list does not require being signed in (#24413) (#24416)
|
* Getting the tag list does not require being signed in (#24413) (#24416)
|
||||||
|
|
||||||
## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/1.19.2) - 2023-04-26
|
## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/v1.19.2) - 2023-04-26
|
||||||
|
|
||||||
* SECURITY
|
* SECURITY
|
||||||
* Require repo scope for PATs for private repos and basic authentication (#24362) (#24364)
|
* Require repo scope for PATs for private repos and basic authentication (#24362) (#24364)
|
||||||
@@ -3569,7 +3653,7 @@ Key highlights of this release encompass significant changes categorized under `
|
|||||||
* Display attachments of review comment when comment content is blank (#23035) (#23046)
|
* Display attachments of review comment when comment content is blank (#23035) (#23046)
|
||||||
* Return empty url for submodule tree entries (#23043) (#23048)
|
* Return empty url for submodule tree entries (#23043) (#23048)
|
||||||
|
|
||||||
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/1.18.4) - 2023-02-20
|
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/v1.18.4) - 2023-02-20
|
||||||
|
|
||||||
* SECURITY
|
* SECURITY
|
||||||
* Provide the ability to set password hash algorithm parameters (#22942) (#22943)
|
* Provide the ability to set password hash algorithm parameters (#22942) (#22943)
|
||||||
@@ -3996,7 +4080,7 @@ Key highlights of this release encompass significant changes categorized under `
|
|||||||
* Fix the mode of custom dir to 0700 in docker-rootless (#20861) (#20867)
|
* Fix the mode of custom dir to 0700 in docker-rootless (#20861) (#20867)
|
||||||
* Fix UI mis-align for PR commit history (#20845) (#20859)
|
* Fix UI mis-align for PR commit history (#20845) (#20859)
|
||||||
|
|
||||||
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17
|
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/v1.17.1) - 2022-08-17
|
||||||
|
|
||||||
* SECURITY
|
* SECURITY
|
||||||
* Correctly escape within tribute.js (#20831) (#20832)
|
* Correctly escape within tribute.js (#20831) (#20832)
|
||||||
|
|||||||
16
Makefile
16
Makefile
@@ -47,6 +47,17 @@ ifeq ($(HAS_GO), yes)
|
|||||||
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
CGO_ENABLED ?= 0
|
||||||
|
ifneq (,$(findstring sqlite,$(TAGS))$(findstring pam,$(TAGS)))
|
||||||
|
CGO_ENABLED = 1
|
||||||
|
endif
|
||||||
|
|
||||||
|
STATIC ?=
|
||||||
|
EXTLDFLAGS ?=
|
||||||
|
ifneq ($(STATIC),)
|
||||||
|
EXTLDFLAGS = -extldflags "-static"
|
||||||
|
endif
|
||||||
|
|
||||||
ifeq ($(GOOS),windows)
|
ifeq ($(GOOS),windows)
|
||||||
IS_WINDOWS := yes
|
IS_WINDOWS := yes
|
||||||
else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows)
|
else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows)
|
||||||
@@ -740,7 +751,10 @@ security-check:
|
|||||||
go run $(GOVULNCHECK_PACKAGE) -show color ./...
|
go run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||||
|
|
||||||
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
|
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),)
|
||||||
|
$(error pam support set via TAGS doesn't support static builds)
|
||||||
|
endif
|
||||||
|
CGO_ENABLED="$(CGO_ENABLED)" CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
|
||||||
|
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check
|
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check
|
||||||
|
|||||||
27
cmd/serv.go
27
cmd/serv.go
@@ -13,7 +13,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
@@ -31,7 +30,6 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/services/lfs"
|
"code.gitea.io/gitea/services/lfs"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/kballard/go-shellquote"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@@ -131,27 +129,6 @@ func getAccessMode(verb, lfsVerb string) perm.AccessMode {
|
|||||||
return perm.AccessModeNone
|
return perm.AccessModeNone
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServCommandResults) (string, error) {
|
|
||||||
now := time.Now()
|
|
||||||
claims := lfs.Claims{
|
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
|
||||||
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
|
|
||||||
NotBefore: jwt.NewNumericDate(now),
|
|
||||||
},
|
|
||||||
RepoID: results.RepoID,
|
|
||||||
Op: lfsVerb,
|
|
||||||
UserID: results.UserID,
|
|
||||||
}
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
||||||
|
|
||||||
// Sign and get the complete encoded token as a string using the secret
|
|
||||||
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
|
|
||||||
if err != nil {
|
|
||||||
return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
|
|
||||||
}
|
|
||||||
return "Bearer " + tokenString, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runServ(c *cli.Context) error {
|
func runServ(c *cli.Context) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -284,7 +261,7 @@ func runServ(c *cli.Context) error {
|
|||||||
|
|
||||||
// LFS SSH protocol
|
// LFS SSH protocol
|
||||||
if verb == git.CmdVerbLfsTransfer {
|
if verb == git.CmdVerbLfsTransfer {
|
||||||
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -295,7 +272,7 @@ func runServ(c *cli.Context) error {
|
|||||||
if verb == git.CmdVerbLfsAuthenticate {
|
if verb == git.CmdVerbLfsAuthenticate {
|
||||||
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
|
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
|
||||||
|
|
||||||
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1747179050,
|
"lastModified": 1752480373,
|
||||||
"narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
|
"narHash": "sha256-JHQbm+OcGp32wAsXTE/FLYGNpb+4GLi5oTvCxwSoBOA=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
|
"rev": "62e0f05ede1da0d54515d4ea8ce9c733f12d9f08",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
30
flake.nix
30
flake.nix
@@ -11,8 +11,16 @@
|
|||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default =
|
||||||
buildInputs = with pkgs; [
|
with pkgs;
|
||||||
|
let
|
||||||
|
# only bump toolchain versions here
|
||||||
|
go = go_1_24;
|
||||||
|
nodejs = nodejs_24;
|
||||||
|
python3 = python312;
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = [
|
||||||
# generic
|
# generic
|
||||||
git
|
git
|
||||||
git-lfs
|
git-lfs
|
||||||
@@ -22,21 +30,25 @@
|
|||||||
gzip
|
gzip
|
||||||
|
|
||||||
# frontend
|
# frontend
|
||||||
nodejs_22
|
nodejs
|
||||||
|
|
||||||
# linting
|
# linting
|
||||||
python312
|
python3
|
||||||
poetry
|
poetry
|
||||||
|
|
||||||
# backend
|
# backend
|
||||||
go_1_24
|
go
|
||||||
|
glibc.static
|
||||||
gofumpt
|
gofumpt
|
||||||
sqlite
|
sqlite
|
||||||
];
|
];
|
||||||
shellHook = ''
|
CFLAGS = "-I${glibc.static.dev}/include";
|
||||||
export GO="${pkgs.go_1_24}/bin/go"
|
LDFLAGS = "-L ${glibc.static}/lib";
|
||||||
export GOROOT="${pkgs.go_1_24}/share/go"
|
GO = "${go}/bin/go";
|
||||||
'';
|
GOROOT = "${go}/share/go";
|
||||||
|
|
||||||
|
TAGS = "sqlite sqlite_unlock_notify";
|
||||||
|
STATIC = "true";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
20
go.mod
20
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module code.gitea.io/gitea
|
module code.gitea.io/gitea
|
||||||
|
|
||||||
go 1.24
|
go 1.24.9
|
||||||
|
|
||||||
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
|
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
|
||||||
// But some CAs use negative serial number, just relax the check. related:
|
// But some CAs use negative serial number, just relax the check. related:
|
||||||
@@ -109,23 +109,23 @@ require (
|
|||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/syndtr/goleveldb v1.0.0
|
github.com/syndtr/goleveldb v1.0.0
|
||||||
github.com/tstranex/u2f v1.0.0
|
github.com/tstranex/u2f v1.0.0
|
||||||
github.com/ulikunitz/xz v0.5.12
|
github.com/ulikunitz/xz v0.5.15
|
||||||
github.com/urfave/cli/v2 v2.27.6
|
github.com/urfave/cli/v2 v2.27.6
|
||||||
github.com/wneessen/go-mail v0.6.2
|
github.com/wneessen/go-mail v0.7.2
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0
|
github.com/xeipuuv/gojsonschema v1.2.0
|
||||||
github.com/yohcop/openid-go v1.0.1
|
github.com/yohcop/openid-go v1.0.1
|
||||||
github.com/yuin/goldmark v1.7.10
|
github.com/yuin/goldmark v1.7.10
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
github.com/yuin/goldmark-meta v1.1.0
|
github.com/yuin/goldmark-meta v1.1.0
|
||||||
gitlab.com/gitlab-org/api/client-go v0.127.0
|
gitlab.com/gitlab-org/api/client-go v0.127.0
|
||||||
golang.org/x/crypto v0.37.0
|
golang.org/x/crypto v0.41.0
|
||||||
golang.org/x/image v0.26.0
|
golang.org/x/image v0.26.0
|
||||||
golang.org/x/net v0.39.0
|
golang.org/x/net v0.43.0
|
||||||
golang.org/x/oauth2 v0.29.0
|
golang.org/x/oauth2 v0.29.0
|
||||||
golang.org/x/sync v0.13.0
|
golang.org/x/sync v0.17.0
|
||||||
golang.org/x/sys v0.32.0
|
golang.org/x/sys v0.35.0
|
||||||
golang.org/x/text v0.24.0
|
golang.org/x/text v0.29.0
|
||||||
golang.org/x/tools v0.32.0
|
golang.org/x/tools v0.36.0
|
||||||
google.golang.org/grpc v1.72.0
|
google.golang.org/grpc v1.72.0
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.6
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
@@ -306,7 +306,7 @@ require (
|
|||||||
go.uber.org/zap v1.27.0 // indirect
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
go.uber.org/zap/exp v0.3.0 // indirect
|
go.uber.org/zap/exp v0.3.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||||
golang.org/x/mod v0.24.0 // indirect
|
golang.org/x/mod v0.27.0 // indirect
|
||||||
golang.org/x/time v0.11.0 // indirect
|
golang.org/x/time v0.11.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
|||||||
45
go.sum
45
go.sum
@@ -757,8 +757,8 @@ github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGB
|
|||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
|
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
|
||||||
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
|
||||||
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
||||||
@@ -766,8 +766,8 @@ github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5
|
|||||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/wneessen/go-mail v0.6.2 h1:c6V7c8D2mz868z9WJ+8zDKtUyLfZ1++uAZmo2GRFji8=
|
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
|
||||||
github.com/wneessen/go-mail v0.6.2/go.mod h1:L/PYjPK3/2ZlNb2/FjEBIn9n1rUWjW+Toy531oVmeb4=
|
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
@@ -834,9 +834,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
|||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||||
@@ -850,8 +849,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -869,8 +868,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -885,9 +884,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -919,9 +917,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -932,9 +929,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
|||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
|
||||||
golang.org/x/text v0.3.0/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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -945,9 +941,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
|
||||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -960,8 +955,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -185,10 +185,10 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status {
|
|||||||
return StatusSuccess
|
return StatusSuccess
|
||||||
case hasCancelled:
|
case hasCancelled:
|
||||||
return StatusCancelled
|
return StatusCancelled
|
||||||
case hasFailure:
|
|
||||||
return StatusFailure
|
|
||||||
case hasRunning:
|
case hasRunning:
|
||||||
return StatusRunning
|
return StatusRunning
|
||||||
|
case hasFailure:
|
||||||
|
return StatusFailure
|
||||||
case hasWaiting:
|
case hasWaiting:
|
||||||
return StatusWaiting
|
return StatusWaiting
|
||||||
case hasBlocked:
|
case hasBlocked:
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ func TestAggregateJobStatus(t *testing.T) {
|
|||||||
{[]Status{StatusCancelled, StatusRunning}, StatusCancelled},
|
{[]Status{StatusCancelled, StatusRunning}, StatusCancelled},
|
||||||
{[]Status{StatusCancelled, StatusBlocked}, StatusCancelled},
|
{[]Status{StatusCancelled, StatusBlocked}, StatusCancelled},
|
||||||
|
|
||||||
// failure with other status, fail fast
|
// failure with other status, usually fail fast, but "running" wins to match GitHub's behavior
|
||||||
// Should "running" win? Maybe no: old code does make "running" win, but GitHub does fail fast.
|
// another reason that we can't make "failure" wins over "running": it would cause a weird behavior that user cannot cancel a workflow or get current running workflows correctly by filter after a job fail.
|
||||||
{[]Status{StatusFailure}, StatusFailure},
|
{[]Status{StatusFailure}, StatusFailure},
|
||||||
{[]Status{StatusFailure, StatusSuccess}, StatusFailure},
|
{[]Status{StatusFailure, StatusSuccess}, StatusFailure},
|
||||||
{[]Status{StatusFailure, StatusSkipped}, StatusFailure},
|
{[]Status{StatusFailure, StatusSkipped}, StatusFailure},
|
||||||
{[]Status{StatusFailure, StatusCancelled}, StatusCancelled},
|
{[]Status{StatusFailure, StatusCancelled}, StatusCancelled},
|
||||||
{[]Status{StatusFailure, StatusWaiting}, StatusFailure},
|
{[]Status{StatusFailure, StatusWaiting}, StatusFailure},
|
||||||
{[]Status{StatusFailure, StatusRunning}, StatusFailure},
|
{[]Status{StatusFailure, StatusRunning}, StatusRunning},
|
||||||
{[]Status{StatusFailure, StatusBlocked}, StatusFailure},
|
{[]Status{StatusFailure, StatusBlocked}, StatusFailure},
|
||||||
|
|
||||||
// skipped with other status
|
// skipped with other status
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature str
|
|||||||
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
|
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to validate token signature. Error: %v", err)
|
log.Debug("AddGPGKey CheckArmoredDetachedSignature failed: %v", err)
|
||||||
return nil, ErrGPGInvalidTokenSignature{
|
return nil, ErrGPGInvalidTokenSignature{
|
||||||
ID: ekeys[0].PrimaryKey.KeyIdString(),
|
ID: ekeys[0].PrimaryKey.KeyIdString(),
|
||||||
Wrapped: err,
|
Wrapped: err,
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st
|
|||||||
}
|
}
|
||||||
|
|
||||||
if signer == nil {
|
if signer == nil {
|
||||||
log.Error("Unable to validate token signature. Error: %v", err)
|
log.Debug("VerifyGPGKey failed: no signer")
|
||||||
return "", ErrGPGInvalidTokenSignature{
|
return "", ErrGPGInvalidTokenSignature{
|
||||||
ID: key.KeyID,
|
ID: key.KeyID,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,13 +67,6 @@ func (key *PublicKey) OmitEmail() string {
|
|||||||
return strings.Join(strings.Split(key.Content, " ")[:2], " ")
|
return strings.Join(strings.Split(key.Content, " ")[:2], " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizedString returns formatted public key string for authorized_keys file.
|
|
||||||
//
|
|
||||||
// TODO: Consider dropping this function
|
|
||||||
func (key *PublicKey) AuthorizedString() string {
|
|
||||||
return AuthorizedStringForKey(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addKey(ctx context.Context, key *PublicKey) (err error) {
|
func addKey(ctx context.Context, key *PublicKey) (err error) {
|
||||||
if len(key.Fingerprint) == 0 {
|
if len(key.Fingerprint) == 0 {
|
||||||
key.Fingerprint, err = CalcFingerprint(key.Content)
|
key.Fingerprint, err = CalcFingerprint(key.Content)
|
||||||
|
|||||||
@@ -17,29 +17,13 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// _____ __ .__ .__ .___
|
// AuthorizedStringCommentPrefix is a magic tag
|
||||||
// / _ \ __ ___/ |_| |__ ___________|__|_______ ____ __| _/
|
// some functions like RegeneratePublicKeys needs this tag to skip the keys generated by Gitea, while keep other keys
|
||||||
// / /_\ \| | \ __\ | \ / _ \_ __ \ \___ // __ \ / __ |
|
const AuthorizedStringCommentPrefix = `# gitea public key`
|
||||||
// / | \ | /| | | Y ( <_> ) | \/ |/ /\ ___// /_/ |
|
|
||||||
// \____|__ /____/ |__| |___| /\____/|__| |__/_____ \\___ >____ |
|
|
||||||
// \/ \/ \/ \/ \/
|
|
||||||
// ____ __.
|
|
||||||
// | |/ _|____ ___.__. ______
|
|
||||||
// | <_/ __ < | |/ ___/
|
|
||||||
// | | \ ___/\___ |\___ \
|
|
||||||
// |____|__ \___ > ____/____ >
|
|
||||||
// \/ \/\/ \/
|
|
||||||
//
|
|
||||||
// This file contains functions for creating authorized_keys files
|
|
||||||
//
|
|
||||||
// There is a dependence on the database within RegeneratePublicKeys however most of these functions probably belong in a module
|
|
||||||
|
|
||||||
const (
|
|
||||||
tplCommentPrefix = `# gitea public key`
|
|
||||||
tplPublicKey = tplCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s` + "\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
var sshOpLocker sync.Mutex
|
var sshOpLocker sync.Mutex
|
||||||
|
|
||||||
@@ -50,17 +34,45 @@ func WithSSHOpLocker(f func() error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
|
// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
|
||||||
func AuthorizedStringForKey(key *PublicKey) string {
|
func AuthorizedStringForKey(key *PublicKey) (string, error) {
|
||||||
sb := &strings.Builder{}
|
sb := &strings.Builder{}
|
||||||
_ = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sb, map[string]any{
|
_, err := writeAuthorizedStringForKey(key, sb)
|
||||||
|
return sb.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteAuthorizedStringForValidKey writes the authorized key for the provided key. If the key is invalid, it does nothing.
|
||||||
|
func WriteAuthorizedStringForValidKey(key *PublicKey, w io.Writer) error {
|
||||||
|
validKey, err := writeAuthorizedStringForKey(key, w)
|
||||||
|
if !validKey {
|
||||||
|
log.Debug("WriteAuthorizedStringForValidKey: key %s is not valid: %v", key, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeAuthorizedStringForKey(key *PublicKey, w io.Writer) (keyValid bool, err error) {
|
||||||
|
const tpl = AuthorizedStringCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s %s` + "\n"
|
||||||
|
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key.Content))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// now the key is valid, the code below could only return template/IO related errors
|
||||||
|
sbCmd := &strings.Builder{}
|
||||||
|
err = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sbCmd, map[string]any{
|
||||||
"AppPath": util.ShellEscape(setting.AppPath),
|
"AppPath": util.ShellEscape(setting.AppPath),
|
||||||
"AppWorkPath": util.ShellEscape(setting.AppWorkPath),
|
"AppWorkPath": util.ShellEscape(setting.AppWorkPath),
|
||||||
"CustomConf": util.ShellEscape(setting.CustomConf),
|
"CustomConf": util.ShellEscape(setting.CustomConf),
|
||||||
"CustomPath": util.ShellEscape(setting.CustomPath),
|
"CustomPath": util.ShellEscape(setting.CustomPath),
|
||||||
"Key": key,
|
"Key": key,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Sprintf(tplPublicKey, util.ShellEscape(sb.String()), key.Content)
|
return true, err
|
||||||
|
}
|
||||||
|
sshCommandEscaped := util.ShellEscape(sbCmd.String())
|
||||||
|
sshKeyMarshalled := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pubKey)))
|
||||||
|
sshKeyComment := fmt.Sprintf("user-%d", key.OwnerID)
|
||||||
|
_, err = fmt.Fprintf(w, tpl, sshCommandEscaped, sshKeyMarshalled, sshKeyComment)
|
||||||
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
|
// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
|
||||||
@@ -112,7 +124,7 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
|
|||||||
if key.Type == KeyTypePrincipal {
|
if key.Type == KeyTypePrincipal {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, err = f.WriteString(key.AuthorizedString()); err != nil {
|
if err = WriteAuthorizedStringForValidKey(key, f); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,10 +132,9 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RegeneratePublicKeys regenerates the authorized_keys file
|
// RegeneratePublicKeys regenerates the authorized_keys file
|
||||||
func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
|
func RegeneratePublicKeys(ctx context.Context, t io.Writer) error {
|
||||||
if err := db.GetEngine(ctx).Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) {
|
if err := db.GetEngine(ctx).Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) {
|
||||||
_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
|
return WriteAuthorizedStringForValidKey(bean.(*PublicKey), t)
|
||||||
return err
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -144,11 +155,11 @@ func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
|
|||||||
scanner := bufio.NewScanner(f)
|
scanner := bufio.NewScanner(f)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if strings.HasPrefix(line, tplCommentPrefix) {
|
if strings.HasPrefix(line, AuthorizedStringCommentPrefix) {
|
||||||
scanner.Scan()
|
scanner.Scan()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, err = t.WriteString(line + "\n")
|
_, err = io.WriteString(t, line+"\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) {
|
|||||||
|
|
||||||
// The ssh library can parse the key, so next we find out what key exactly we have.
|
// The ssh library can parse the key, so next we find out what key exactly we have.
|
||||||
switch pkey.Type() {
|
switch pkey.Type() {
|
||||||
case ssh.KeyAlgoDSA:
|
case ssh.KeyAlgoDSA: //nolint:staticcheck // it's deprecated
|
||||||
rawPub := struct {
|
rawPub := struct {
|
||||||
Name string
|
Name string
|
||||||
P, Q, G, Y *big.Int
|
P, Q, G, Y *big.Int
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func VerifySSHKey(ctx context.Context, ownerID int64, fingerprint, token, signat
|
|||||||
// edge case for Windows based shells that will add CR LF if piped to ssh-keygen command
|
// edge case for Windows based shells that will add CR LF if piped to ssh-keygen command
|
||||||
// see https://github.com/PowerShell/PowerShell/issues/5974
|
// see https://github.com/PowerShell/PowerShell/issues/5974
|
||||||
if sshsig.Verify(strings.NewReader(token+"\r\n"), []byte(signature), []byte(key.Content), "gitea") != nil {
|
if sshsig.Verify(strings.NewReader(token+"\r\n"), []byte(signature), []byte(key.Content), "gitea") != nil {
|
||||||
log.Error("Unable to validate token signature. Error: %v", err)
|
log.Debug("VerifySSHKey sshsig.Verify failed: %v", err)
|
||||||
return "", ErrSSHInvalidTokenSignature{
|
return "", ErrSSHInvalidTokenSignature{
|
||||||
Fingerprint: key.Fingerprint,
|
Fingerprint: key.Fingerprint,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -518,7 +518,7 @@ func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, curre
|
|||||||
return currentWhitelist, nil
|
return currentWhitelist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead)
|
teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err)
|
return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -719,7 +719,8 @@ func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Comment) loadReview(ctx context.Context) (err error) {
|
// LoadReview loads the associated review
|
||||||
|
func (c *Comment) LoadReview(ctx context.Context) (err error) {
|
||||||
if c.ReviewID == 0 {
|
if c.ReviewID == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -736,11 +737,6 @@ func (c *Comment) loadReview(ctx context.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadReview loads the associated review
|
|
||||||
func (c *Comment) LoadReview(ctx context.Context) error {
|
|
||||||
return c.loadReview(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes.
|
// DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes.
|
||||||
func (c *Comment) DiffSide() string {
|
func (c *Comment) DiffSide() string {
|
||||||
if c.Line < 0 {
|
if c.Line < 0 {
|
||||||
@@ -860,7 +856,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
|
|||||||
}
|
}
|
||||||
if comment.ReviewID != 0 {
|
if comment.ReviewID != 0 {
|
||||||
if comment.Review == nil {
|
if comment.Review == nil {
|
||||||
if err := comment.loadReview(ctx); err != nil {
|
if err := comment.LoadReview(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe
|
|||||||
|
|
||||||
// AddCrossReferences add cross references
|
// AddCrossReferences add cross references
|
||||||
func (c *Comment) AddCrossReferences(stdCtx context.Context, doer *user_model.User, removeOld bool) error {
|
func (c *Comment) AddCrossReferences(stdCtx context.Context, doer *user_model.User, removeOld bool) error {
|
||||||
if c.Type != CommentTypeCode && c.Type != CommentTypeComment {
|
if !c.Type.HasContentSupport() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := c.LoadIssue(stdCtx); err != nil {
|
if err := c.LoadIssue(stdCtx); err != nil {
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ func GetReviewsByIssueID(ctx context.Context, issueID int64) (latestReviews, mig
|
|||||||
reviewersMap := make(map[int64][]*Review) // key is reviewer id
|
reviewersMap := make(map[int64][]*Review) // key is reviewer id
|
||||||
originalReviewersMap := make(map[int64][]*Review) // key is original author id
|
originalReviewersMap := make(map[int64][]*Review) // key is original author id
|
||||||
reviewTeamsMap := make(map[int64][]*Review) // key is reviewer team id
|
reviewTeamsMap := make(map[int64][]*Review) // key is reviewer team id
|
||||||
countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest}
|
countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, ReviewTypeComment}
|
||||||
for _, review := range reviews {
|
for _, review := range reviews {
|
||||||
if review.ReviewerTeamID == 0 && slices.Contains(countedReivewTypes, review.Type) && !review.Dismissed {
|
if review.ReviewerTeamID == 0 && slices.Contains(countedReivewTypes, review.Type) && !review.Dismissed {
|
||||||
if review.OriginalAuthorID != 0 {
|
if review.OriginalAuthorID != 0 {
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ func TestGetReviewersByIssueID(t *testing.T) {
|
|||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||||
|
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||||
@@ -130,6 +131,12 @@ func TestGetReviewersByIssueID(t *testing.T) {
|
|||||||
|
|
||||||
expectedReviews := []*issues_model.Review{}
|
expectedReviews := []*issues_model.Review{}
|
||||||
expectedReviews = append(expectedReviews,
|
expectedReviews = append(expectedReviews,
|
||||||
|
&issues_model.Review{
|
||||||
|
ID: 5,
|
||||||
|
Reviewer: user1,
|
||||||
|
Type: issues_model.ReviewTypeComment,
|
||||||
|
UpdatedUnix: 946684810,
|
||||||
|
},
|
||||||
&issues_model.Review{
|
&issues_model.Review{
|
||||||
ID: 7,
|
ID: 7,
|
||||||
Reviewer: org3,
|
Reviewer: org3,
|
||||||
@@ -168,8 +175,9 @@ func TestGetReviewersByIssueID(t *testing.T) {
|
|||||||
for _, review := range allReviews {
|
for _, review := range allReviews {
|
||||||
assert.NoError(t, review.LoadReviewer(db.DefaultContext))
|
assert.NoError(t, review.LoadReviewer(db.DefaultContext))
|
||||||
}
|
}
|
||||||
if assert.Len(t, allReviews, 5) {
|
if assert.Len(t, allReviews, 6) {
|
||||||
for i, review := range allReviews {
|
for i, review := range allReviews {
|
||||||
|
assert.Equal(t, expectedReviews[i].ID, review.ID)
|
||||||
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
|
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
|
||||||
assert.Equal(t, expectedReviews[i].Type, review.Type)
|
assert.Equal(t, expectedReviews[i].Type, review.Type)
|
||||||
assert.Equal(t, expectedReviews[i].UpdatedUnix, review.UpdatedUnix)
|
assert.Equal(t, expectedReviews[i].UpdatedUnix, review.UpdatedUnix)
|
||||||
|
|||||||
@@ -602,8 +602,3 @@ func getUserTeamIDsQueryBuilder(orgID, userID int64) *builder.Builder {
|
|||||||
"team_user.uid": userID,
|
"team_user.uid": userID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TeamsWithAccessToRepo returns all teams that have given access level to the repository.
|
|
||||||
func (org *Organization) TeamsWithAccessToRepo(ctx context.Context, repoID int64, mode perm.AccessMode) ([]*Team, error) {
|
|
||||||
return GetTeamsWithAccessToRepo(ctx, org.ID, repoID, mode)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TeamRepo represents an team-repository relation.
|
// TeamRepo represents an team-repository relation.
|
||||||
@@ -48,26 +50,27 @@ func RemoveTeamRepo(ctx context.Context, teamID, repoID int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTeamsWithAccessToRepo returns all teams in an organization that have given access level to the repository.
|
// GetTeamsWithAccessToAnyRepoUnit returns all teams in an organization that have given access level to the repository special unit.
|
||||||
func GetTeamsWithAccessToRepo(ctx context.Context, orgID, repoID int64, mode perm.AccessMode) ([]*Team, error) {
|
// This function is only used for finding some teams that can be used as branch protection allowlist or reviewers, it isn't really used for access control.
|
||||||
|
// FIXME: TEAM-UNIT-PERMISSION this logic is not complete, search the fixme keyword to see more details
|
||||||
|
func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) ([]*Team, error) {
|
||||||
teams := make([]*Team, 0, 5)
|
teams := make([]*Team, 0, 5)
|
||||||
return teams, db.GetEngine(ctx).Where("team.authorize >= ?", mode).
|
|
||||||
Join("INNER", "team_repo", "team_repo.team_id = team.id").
|
|
||||||
And("team_repo.org_id = ?", orgID).
|
|
||||||
And("team_repo.repo_id = ?", repoID).
|
|
||||||
OrderBy("name").
|
|
||||||
Find(&teams)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTeamsWithAccessToRepoUnit returns all teams in an organization that have given access level to the repository special unit.
|
sub := builder.Select("team_id").From("team_unit").
|
||||||
func GetTeamsWithAccessToRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type) ([]*Team, error) {
|
Where(builder.Expr("team_unit.team_id = team.id")).
|
||||||
teams := make([]*Team, 0, 5)
|
And(builder.In("team_unit.type", append([]unit.Type{unitType}, unitTypesMore...))).
|
||||||
return teams, db.GetEngine(ctx).Where("team_unit.access_mode >= ?", mode).
|
And(builder.Expr("team_unit.access_mode >= ?", mode))
|
||||||
|
|
||||||
|
err := db.GetEngine(ctx).
|
||||||
Join("INNER", "team_repo", "team_repo.team_id = team.id").
|
Join("INNER", "team_repo", "team_repo.team_id = team.id").
|
||||||
Join("INNER", "team_unit", "team_unit.team_id = team.id").
|
|
||||||
And("team_repo.org_id = ?", orgID).
|
And("team_repo.org_id = ?", orgID).
|
||||||
And("team_repo.repo_id = ?", repoID).
|
And("team_repo.repo_id = ?", repoID).
|
||||||
And("team_unit.type = ?", unitType).
|
And(builder.Or(
|
||||||
|
builder.Expr("team.authorize >= ?", mode),
|
||||||
|
builder.In("team.id", sub),
|
||||||
|
)).
|
||||||
OrderBy("name").
|
OrderBy("name").
|
||||||
Find(&teams)
|
Find(&teams)
|
||||||
|
|
||||||
|
return teams, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func TestGetTeamsWithAccessToRepoUnit(t *testing.T) {
|
|||||||
org41 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 41})
|
org41 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 41})
|
||||||
repo61 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 61})
|
repo61 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 61})
|
||||||
|
|
||||||
teams, err := organization.GetTeamsWithAccessToRepoUnit(db.DefaultContext, org41.ID, repo61.ID, perm.AccessModeRead, unit.TypePullRequests)
|
teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(db.DefaultContext, org41.ID, repo61.ID, perm.AccessModeRead, unit.TypePullRequests)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
if assert.Len(t, teams, 2) {
|
if assert.Len(t, teams, 2) {
|
||||||
assert.EqualValues(t, 21, teams[0].ID)
|
assert.EqualValues(t, 21, teams[0].ID)
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ func (p *Permission) IsAdmin() bool {
|
|||||||
|
|
||||||
// HasAnyUnitAccess returns true if the user might have at least one access mode to any unit of this repository.
|
// HasAnyUnitAccess returns true if the user might have at least one access mode to any unit of this repository.
|
||||||
// It doesn't count the "public(anonymous/everyone) access mode".
|
// It doesn't count the "public(anonymous/everyone) access mode".
|
||||||
|
// TODO: most calls to this function should be replaced with `HasAnyUnitAccessOrPublicAccess`
|
||||||
func (p *Permission) HasAnyUnitAccess() bool {
|
func (p *Permission) HasAnyUnitAccess() bool {
|
||||||
for _, v := range p.unitsMode {
|
for _, v := range p.unitsMode {
|
||||||
if v >= perm_model.AccessModeRead {
|
if v >= perm_model.AccessModeRead {
|
||||||
@@ -267,7 +268,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
|||||||
perm.units = repo.Units
|
perm.units = repo.Units
|
||||||
|
|
||||||
// anonymous user visit private repo.
|
// anonymous user visit private repo.
|
||||||
// TODO: anonymous user visit public unit of private repo???
|
|
||||||
if user == nil && repo.IsPrivate {
|
if user == nil && repo.IsPrivate {
|
||||||
perm.AccessMode = perm_model.AccessModeNone
|
perm.AccessMode = perm_model.AccessModeNone
|
||||||
return perm, nil
|
return perm, nil
|
||||||
@@ -286,7 +286,8 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prevent strangers from checking out public repo of private organization/users
|
// Prevent strangers from checking out public repo of private organization/users
|
||||||
// Allow user if they are collaborator of a repo within a private user or a private organization but not a member of the organization itself
|
// Allow user if they are a collaborator of a repo within a private user or a private organization but not a member of the organization itself
|
||||||
|
// TODO: rename it to "IsOwnerVisibleToDoer"
|
||||||
if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !isCollaborator {
|
if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !isCollaborator {
|
||||||
perm.AccessMode = perm_model.AccessModeNone
|
perm.AccessMode = perm_model.AccessModeNone
|
||||||
return perm, nil
|
return perm, nil
|
||||||
@@ -304,7 +305,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
|||||||
return perm, nil
|
return perm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// plain user
|
// plain user TODO: this check should be replaced, only need to check collaborator access mode
|
||||||
perm.AccessMode, err = accessLevel(ctx, user, repo)
|
perm.AccessMode, err = accessLevel(ctx, user, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return perm, err
|
return perm, err
|
||||||
@@ -314,6 +315,19 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
|||||||
return perm, nil
|
return perm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now: the owner is visible to doer, if the repo is public, then the min access mode is read
|
||||||
|
minAccessMode := util.Iif(!repo.IsPrivate && !user.IsRestricted, perm_model.AccessModeRead, perm_model.AccessModeNone)
|
||||||
|
perm.AccessMode = max(perm.AccessMode, minAccessMode)
|
||||||
|
|
||||||
|
// get units mode from teams
|
||||||
|
teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
|
||||||
|
if err != nil {
|
||||||
|
return perm, err
|
||||||
|
}
|
||||||
|
if len(teams) == 0 {
|
||||||
|
return perm, nil
|
||||||
|
}
|
||||||
|
|
||||||
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
|
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
|
||||||
|
|
||||||
// Collaborators on organization
|
// Collaborators on organization
|
||||||
@@ -323,12 +337,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get units mode from teams
|
|
||||||
teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
|
|
||||||
if err != nil {
|
|
||||||
return perm, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if user in an owner team
|
// if user in an owner team
|
||||||
for _, team := range teams {
|
for _, team := range teams {
|
||||||
if team.HasAdminAccess() {
|
if team.HasAdminAccess() {
|
||||||
@@ -339,19 +347,10 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range repo.Units {
|
for _, u := range repo.Units {
|
||||||
var found bool
|
|
||||||
for _, team := range teams {
|
for _, team := range teams {
|
||||||
if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist {
|
teamMode, _ := team.UnitAccessModeEx(ctx, u.Type)
|
||||||
perm.unitsMode[u.Type] = max(perm.unitsMode[u.Type], teamMode)
|
unitAccessMode := max(perm.unitsMode[u.Type], minAccessMode, teamMode)
|
||||||
found = true
|
perm.unitsMode[u.Type] = unitAccessMode
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
|
|
||||||
if !found && !repo.IsPrivate && !user.IsRestricted {
|
|
||||||
if _, ok := perm.unitsMode[u.Type]; !ok {
|
|
||||||
perm.unitsMode[u.Type] = perm_model.AccessModeRead
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,16 @@ package access
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
perm_model "code.gitea.io/gitea/models/perm"
|
perm_model "code.gitea.io/gitea/models/perm"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHasAnyUnitAccess(t *testing.T) {
|
func TestHasAnyUnitAccess(t *testing.T) {
|
||||||
@@ -152,3 +156,78 @@ func TestUnitAccessMode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "has unit, and map, use map")
|
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "has unit, and map, use map")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetUserRepoPermission(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
ctx := t.Context()
|
||||||
|
repo32 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32}) // org public repo
|
||||||
|
require.NoError(t, repo32.LoadOwner(ctx))
|
||||||
|
require.True(t, repo32.Owner.IsOrganization())
|
||||||
|
|
||||||
|
require.NoError(t, db.TruncateBeans(ctx, &organization.Team{}, &organization.TeamUser{}, &organization.TeamRepo{}, &organization.TeamUnit{}))
|
||||||
|
org := repo32.Owner
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||||
|
team := &organization.Team{OrgID: org.ID, LowerName: "test_team"}
|
||||||
|
require.NoError(t, db.Insert(ctx, team))
|
||||||
|
|
||||||
|
t.Run("DoerInTeamWithNoRepo", func(t *testing.T) {
|
||||||
|
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
|
||||||
|
perm, err := GetUserRepoPermission(ctx, repo32, user)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
|
||||||
|
assert.Nil(t, perm.unitsMode) // doer in the team, but has no access to the repo
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo32.ID}))
|
||||||
|
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone}))
|
||||||
|
t.Run("DoerWithTeamUnitAccessNone", func(t *testing.T) {
|
||||||
|
perm, err := GetUserRepoPermission(ctx, repo32, user)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
|
||||||
|
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeCode])
|
||||||
|
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{}))
|
||||||
|
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeWrite}))
|
||||||
|
t.Run("DoerWithTeamUnitAccessWrite", func(t *testing.T) {
|
||||||
|
perm, err := GetUserRepoPermission(ctx, repo32, user)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
|
||||||
|
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
|
||||||
|
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
|
||||||
|
})
|
||||||
|
|
||||||
|
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // org private repo, same org as repo 32
|
||||||
|
require.NoError(t, repo3.LoadOwner(ctx))
|
||||||
|
require.True(t, repo3.Owner.IsOrganization())
|
||||||
|
require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{}, &Access{})) // The user has access set of that repo, remove it, it is useless for our test
|
||||||
|
require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo3.ID}))
|
||||||
|
t.Run("DoerWithNoopTeamOnPrivateRepo", func(t *testing.T) {
|
||||||
|
perm, err := GetUserRepoPermission(ctx, repo3, user)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
|
||||||
|
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
|
||||||
|
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeIssues])
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone}))
|
||||||
|
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeIssues, AccessMode: perm_model.AccessModeRead}))
|
||||||
|
t.Run("DoerWithReadIssueTeamOnPrivateRepo", func(t *testing.T) {
|
||||||
|
perm, err := GetUserRepoPermission(ctx, repo3, user)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
|
||||||
|
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
|
||||||
|
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
|
||||||
|
require.NoError(t, db.Insert(ctx, Access{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
|
||||||
|
t.Run("DoerWithReadIssueTeamAndWriteCollaboratorOnPrivateRepo", func(t *testing.T) {
|
||||||
|
perm, err := GetUserRepoPermission(ctx, repo3, user)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
|
||||||
|
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
|
||||||
|
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ package pull
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AutoMerge represents a pull request scheduled for merging when checks succeed
|
// AutoMerge represents a pull request scheduled for merging when checks succeed
|
||||||
@@ -76,7 +78,10 @@ func GetScheduledMergeByPullID(ctx context.Context, pullID int64) (bool, *AutoMe
|
|||||||
return false, nil, err
|
return false, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
doer, err := user_model.GetUserByID(ctx, scheduledPRM.DoerID)
|
doer, err := user_model.GetPossibleUserByID(ctx, scheduledPRM.DoerID)
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
doer, err = user_model.NewGhostUser(), nil
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return false, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ func TestRepository_ChangeCollaborationAccessMode(t *testing.T) {
|
|||||||
|
|
||||||
assert.NoError(t, repo_model.ChangeCollaborationAccessMode(db.DefaultContext, repo, unittest.NonexistentID, perm.AccessModeAdmin))
|
assert.NoError(t, repo_model.ChangeCollaborationAccessMode(db.DefaultContext, repo, unittest.NonexistentID, perm.AccessModeAdmin))
|
||||||
|
|
||||||
// Disvard invalid input.
|
// Discard invalid input.
|
||||||
assert.NoError(t, repo_model.ChangeCollaborationAccessMode(db.DefaultContext, repo, 4, perm.AccessMode(unittest.NonexistentID)))
|
assert.NoError(t, repo_model.ChangeCollaborationAccessMode(db.DefaultContext, repo, 4, perm.AccessMode(-1)))
|
||||||
|
|
||||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
|
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,16 +137,9 @@ func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) {
|
|||||||
|
|
||||||
for _, upload := range uploads {
|
for _, upload := range uploads {
|
||||||
localPath := upload.LocalPath()
|
localPath := upload.LocalPath()
|
||||||
isFile, err := util.IsFile(localPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to check if %s is a file. Error: %v", localPath, err)
|
|
||||||
}
|
|
||||||
if !isFile {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := util.Remove(localPath); err != nil {
|
if err := util.Remove(localPath); err != nil {
|
||||||
return fmt.Errorf("remove upload: %w", err)
|
// just continue, don't fail the whole operation if a file is missing (removed by others)
|
||||||
|
log.Error("unable to remove upload file %s: %v", localPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1176,12 +1176,14 @@ func GetUsersByEmails(ctx context.Context, emails []string) (*EmailUserMap, erro
|
|||||||
|
|
||||||
needCheckEmails := make(container.Set[string])
|
needCheckEmails := make(container.Set[string])
|
||||||
needCheckUserNames := make(container.Set[string])
|
needCheckUserNames := make(container.Set[string])
|
||||||
|
noReplyAddressSuffix := "@" + strings.ToLower(setting.Service.NoReplyAddress)
|
||||||
for _, email := range emails {
|
for _, email := range emails {
|
||||||
if strings.HasSuffix(email, "@"+setting.Service.NoReplyAddress) {
|
emailLower := strings.ToLower(email)
|
||||||
username := strings.TrimSuffix(email, "@"+setting.Service.NoReplyAddress)
|
if noReplyUserNameLower, ok := strings.CutSuffix(emailLower, noReplyAddressSuffix); ok {
|
||||||
needCheckUserNames.Add(strings.ToLower(username))
|
needCheckUserNames.Add(noReplyUserNameLower)
|
||||||
|
needCheckEmails.Add(emailLower)
|
||||||
} else {
|
} else {
|
||||||
needCheckEmails.Add(strings.ToLower(email))
|
needCheckEmails.Add(emailLower)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ func TestUserEmails(t *testing.T) {
|
|||||||
testGetUserByEmail(t, c.Email, c.UID)
|
testGetUserByEmail(t, c.Email, c.UID)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
t.Run("NoReplyConflict", func(t *testing.T) {
|
||||||
|
setting.Service.NoReplyAddress = "example.com"
|
||||||
|
testGetUserByEmail(t, "user1-2@example.COM", 1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,22 +6,26 @@ package fileicon
|
|||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/svg"
|
"code.gitea.io/gitea/modules/svg"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BasicThemeIcon(entry *git.TreeEntry) template.HTML {
|
func BasicEntryIconName(entry *EntryInfo) string {
|
||||||
svgName := "octicon-file"
|
svgName := "octicon-file"
|
||||||
switch {
|
switch {
|
||||||
case entry.IsLink():
|
case entry.EntryMode.IsLink():
|
||||||
svgName = "octicon-file-symlink-file"
|
svgName = "octicon-file-symlink-file"
|
||||||
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
|
if entry.SymlinkToMode.IsDir() {
|
||||||
svgName = "octicon-file-directory-symlink"
|
svgName = "octicon-file-directory-symlink"
|
||||||
}
|
}
|
||||||
case entry.IsDir():
|
case entry.EntryMode.IsDir():
|
||||||
svgName = "octicon-file-directory-fill"
|
svgName = util.Iif(entry.IsOpen, "octicon-file-directory-open-fill", "octicon-file-directory-fill")
|
||||||
case entry.IsSubModule():
|
case entry.EntryMode.IsSubModule():
|
||||||
svgName = "octicon-file-submodule"
|
svgName = "octicon-file-submodule"
|
||||||
}
|
}
|
||||||
return svg.RenderHTML(svgName)
|
return svgName
|
||||||
|
}
|
||||||
|
|
||||||
|
func BasicEntryIconHTML(entry *EntryInfo) template.HTML {
|
||||||
|
return svg.RenderHTML(BasicEntryIconName(entry))
|
||||||
}
|
}
|
||||||
|
|||||||
31
modules/fileicon/entry.go
Normal file
31
modules/fileicon/entry.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package fileicon
|
||||||
|
|
||||||
|
import "code.gitea.io/gitea/modules/git"
|
||||||
|
|
||||||
|
type EntryInfo struct {
|
||||||
|
FullName string
|
||||||
|
EntryMode git.EntryMode
|
||||||
|
SymlinkToMode git.EntryMode
|
||||||
|
IsOpen bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func EntryInfoFromGitTreeEntry(gitEntry *git.TreeEntry) *EntryInfo {
|
||||||
|
ret := &EntryInfo{FullName: gitEntry.Name(), EntryMode: gitEntry.Mode()}
|
||||||
|
if gitEntry.IsLink() {
|
||||||
|
if te, err := gitEntry.FollowLink(); err == nil && te.IsDir() {
|
||||||
|
ret.SymlinkToMode = te.Mode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func EntryInfoFolder() *EntryInfo {
|
||||||
|
return &EntryInfo{EntryMode: git.EntryModeTree}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EntryInfoFolderOpen() *EntryInfo {
|
||||||
|
return &EntryInfo{EntryMode: git.EntryModeTree, IsOpen: true}
|
||||||
|
}
|
||||||
@@ -9,11 +9,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/options"
|
"code.gitea.io/gitea/modules/options"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/svg"
|
"code.gitea.io/gitea/modules/svg"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type materialIconRulesData struct {
|
type materialIconRulesData struct {
|
||||||
@@ -69,42 +70,52 @@ func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg,
|
|||||||
}
|
}
|
||||||
svgID := "svg-mfi-" + name
|
svgID := "svg-mfi-" + name
|
||||||
svgCommonAttrs := `class="svg git-entry-icon ` + extraClass + `" width="16" height="16" aria-hidden="true"`
|
svgCommonAttrs := `class="svg git-entry-icon ` + extraClass + `" width="16" height="16" aria-hidden="true"`
|
||||||
|
svgHTML := template.HTML(`<svg id="` + svgID + `" ` + svgCommonAttrs + svg[4:])
|
||||||
|
if p == nil {
|
||||||
|
return svgHTML
|
||||||
|
}
|
||||||
if p.IconSVGs[svgID] == "" {
|
if p.IconSVGs[svgID] == "" {
|
||||||
p.IconSVGs[svgID] = template.HTML(`<svg id="` + svgID + `" ` + svgCommonAttrs + svg[4:])
|
p.IconSVGs[svgID] = svgHTML
|
||||||
}
|
}
|
||||||
return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
|
return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MaterialIconProvider) FileIcon(p *RenderedIconPool, entry *git.TreeEntry) template.HTML {
|
func (m *MaterialIconProvider) EntryIconHTML(p *RenderedIconPool, entry *EntryInfo) template.HTML {
|
||||||
if m.rules == nil {
|
if m.rules == nil {
|
||||||
return BasicThemeIcon(entry)
|
return BasicEntryIconHTML(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.IsLink() {
|
if entry.EntryMode.IsLink() {
|
||||||
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
|
if entry.SymlinkToMode.IsDir() {
|
||||||
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
|
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
|
||||||
return svg.RenderHTML("material-folder-symlink", 16, "octicon-file-directory-symlink")
|
return svg.RenderHTML("material-folder-symlink", 16, "octicon-file-directory-symlink")
|
||||||
}
|
}
|
||||||
return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them
|
return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them
|
||||||
}
|
}
|
||||||
|
|
||||||
name := m.findIconNameByGit(entry)
|
name := m.FindIconName(entry)
|
||||||
// the material icon pack's "folder" icon doesn't look good, so use our built-in one
|
iconSVG := m.svgs[name]
|
||||||
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
|
if iconSVG == "" {
|
||||||
if iconSVG, ok := m.svgs[name]; ok && name != "folder" && iconSVG != "" {
|
name = "file"
|
||||||
|
if entry.EntryMode.IsDir() {
|
||||||
|
name = util.Iif(entry.IsOpen, "folder-open", "folder")
|
||||||
|
}
|
||||||
|
iconSVG = m.svgs[name]
|
||||||
|
if iconSVG == "" {
|
||||||
|
setting.PanicInDevOrTesting("missing file icon for %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
|
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
|
||||||
extraClass := "octicon-file"
|
extraClass := "octicon-file"
|
||||||
switch {
|
switch {
|
||||||
case entry.IsDir():
|
case entry.EntryMode.IsDir():
|
||||||
extraClass = "octicon-file-directory-fill"
|
extraClass = BasicEntryIconName(entry)
|
||||||
case entry.IsSubModule():
|
case entry.EntryMode.IsSubModule():
|
||||||
extraClass = "octicon-file-submodule"
|
extraClass = "octicon-file-submodule"
|
||||||
}
|
}
|
||||||
return m.renderFileIconSVG(p, name, iconSVG, extraClass)
|
return m.renderFileIconSVG(p, name, iconSVG, extraClass)
|
||||||
}
|
}
|
||||||
// TODO: use an interface or wrapper for git.Entry to make the code testable.
|
|
||||||
return BasicThemeIcon(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
|
func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
|
||||||
if _, ok := m.svgs[s]; ok {
|
if _, ok := m.svgs[s]; ok {
|
||||||
@@ -118,13 +129,17 @@ func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
|
func (m *MaterialIconProvider) FindIconName(entry *EntryInfo) string {
|
||||||
fileNameLower := strings.ToLower(path.Base(name))
|
if entry.EntryMode.IsSubModule() {
|
||||||
if isDir {
|
return "folder-git"
|
||||||
|
}
|
||||||
|
|
||||||
|
fileNameLower := strings.ToLower(path.Base(entry.FullName))
|
||||||
|
if entry.EntryMode.IsDir() {
|
||||||
if s, ok := m.rules.FolderNames[fileNameLower]; ok {
|
if s, ok := m.rules.FolderNames[fileNameLower]; ok {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
return "folder"
|
return util.Iif(entry.IsOpen, "folder-open", "folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, ok := m.rules.FileNames[fileNameLower]; ok {
|
if s, ok := m.rules.FileNames[fileNameLower]; ok {
|
||||||
@@ -146,10 +161,3 @@ func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
|
|||||||
|
|
||||||
return "file"
|
return "file"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MaterialIconProvider) findIconNameByGit(entry *git.TreeEntry) string {
|
|
||||||
if entry.IsSubModule() {
|
|
||||||
return "folder-git"
|
|
||||||
}
|
|
||||||
return m.FindIconName(entry.Name(), entry.IsDir())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/fileicon"
|
"code.gitea.io/gitea/modules/fileicon"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -19,8 +20,8 @@ func TestMain(m *testing.M) {
|
|||||||
func TestFindIconName(t *testing.T) {
|
func TestFindIconName(t *testing.T) {
|
||||||
unittest.PrepareTestEnv(t)
|
unittest.PrepareTestEnv(t)
|
||||||
p := fileicon.DefaultMaterialIconProvider()
|
p := fileicon.DefaultMaterialIconProvider()
|
||||||
assert.Equal(t, "php", p.FindIconName("foo.php", false))
|
assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.php", EntryMode: git.EntryModeBlob}))
|
||||||
assert.Equal(t, "php", p.FindIconName("foo.PHP", false))
|
assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.PHP", EntryMode: git.EntryModeBlob}))
|
||||||
assert.Equal(t, "javascript", p.FindIconName("foo.js", false))
|
assert.Equal(t, "javascript", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.js", EntryMode: git.EntryModeBlob}))
|
||||||
assert.Equal(t, "visualstudio", p.FindIconName("foo.vba", false))
|
assert.Equal(t, "visualstudio", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.vba", EntryMode: git.EntryModeBlob}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,19 +33,9 @@ func (p *RenderedIconPool) RenderToHTML() template.HTML {
|
|||||||
return template.HTML(sb.String())
|
return template.HTML(sb.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use an interface or struct to replace "*git.TreeEntry", to decouple the fileicon module from git module
|
func RenderEntryIconHTML(renderedIconPool *RenderedIconPool, entry *EntryInfo) template.HTML {
|
||||||
|
|
||||||
func RenderEntryIcon(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML {
|
|
||||||
if setting.UI.FileIconTheme == "material" {
|
if setting.UI.FileIconTheme == "material" {
|
||||||
return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry)
|
return DefaultMaterialIconProvider().EntryIconHTML(renderedIconPool, entry)
|
||||||
}
|
}
|
||||||
return BasicThemeIcon(entry)
|
return BasicEntryIconHTML(entry)
|
||||||
}
|
|
||||||
|
|
||||||
func RenderEntryIconOpen(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML {
|
|
||||||
// TODO: add "open icon" support
|
|
||||||
if setting.UI.FileIconTheme == "material" {
|
|
||||||
return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry)
|
|
||||||
}
|
|
||||||
return BasicThemeIcon(entry)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const (
|
|||||||
GitlabLanguage = "gitlab-language"
|
GitlabLanguage = "gitlab-language"
|
||||||
Lockable = "lockable"
|
Lockable = "lockable"
|
||||||
Filter = "filter"
|
Filter = "filter"
|
||||||
|
Diff = "diff"
|
||||||
)
|
)
|
||||||
|
|
||||||
var LinguistAttributes = []string{
|
var LinguistAttributes = []string{
|
||||||
|
|||||||
@@ -9,3 +9,15 @@ type CommitInfo struct {
|
|||||||
Commit *Commit
|
Commit *Commit
|
||||||
SubmoduleFile *CommitSubmoduleFile
|
SubmoduleFile *CommitSubmoduleFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCommitInfoSubmoduleFile(repoLink, fullPath string, commit *Commit, refCommitID ObjectID) (*CommitSubmoduleFile, error) {
|
||||||
|
submodule, err := commit.GetSubModule(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if submodule == nil {
|
||||||
|
// unable to find submodule from ".gitmodules" file
|
||||||
|
return NewCommitSubmoduleFile(repoLink, fullPath, "", refCommitID.String()), nil
|
||||||
|
}
|
||||||
|
return NewCommitSubmoduleFile(repoLink, fullPath, submodule.URL, refCommitID.String()), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// GetCommitsInfo gets information of all commits that are corresponding to these entries
|
// GetCommitsInfo gets information of all commits that are corresponding to these entries
|
||||||
func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
|
func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
|
||||||
entryPaths := make([]string, len(tes)+1)
|
entryPaths := make([]string, len(tes)+1)
|
||||||
// Get the commit for the treePath itself
|
// Get the commit for the treePath itself
|
||||||
entryPaths[0] = ""
|
entryPaths[0] = ""
|
||||||
@@ -71,22 +71,12 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
|||||||
commitsInfo[i].Commit = entryCommit
|
commitsInfo[i].Commit = entryCommit
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the entry is a submodule add a submodule file for this
|
// If the entry is a submodule, add a submodule file for this
|
||||||
if entry.IsSubModule() {
|
if entry.IsSubModule() {
|
||||||
subModuleURL := ""
|
commitsInfo[i].SubmoduleFile, err = GetCommitInfoSubmoduleFile(repoLink, path.Join(treePath, entry.Name()), commit, entry.ID)
|
||||||
var fullPath string
|
if err != nil {
|
||||||
if len(treePath) > 0 {
|
|
||||||
fullPath = treePath + "/" + entry.Name()
|
|
||||||
} else {
|
|
||||||
fullPath = entry.Name()
|
|
||||||
}
|
|
||||||
if subModule, err := commit.GetSubModule(fullPath); err != nil {
|
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
} else if subModule != nil {
|
|
||||||
subModuleURL = subModule.URL
|
|
||||||
}
|
}
|
||||||
subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String())
|
|
||||||
commitsInfo[i].SubmoduleFile = subModuleFile
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// GetCommitsInfo gets information of all commits that are corresponding to these entries
|
// GetCommitsInfo gets information of all commits that are corresponding to these entries
|
||||||
func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
|
func (tes Entries) GetCommitsInfo(ctx context.Context, repoLink string, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
|
||||||
entryPaths := make([]string, len(tes)+1)
|
entryPaths := make([]string, len(tes)+1)
|
||||||
// Get the commit for the treePath itself
|
// Get the commit for the treePath itself
|
||||||
entryPaths[0] = ""
|
entryPaths[0] = ""
|
||||||
@@ -65,22 +65,12 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
|||||||
log.Debug("missing commit for %s", entry.Name())
|
log.Debug("missing commit for %s", entry.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the entry is a submodule add a submodule file for this
|
// If the entry is a submodule, add a submodule file for this
|
||||||
if entry.IsSubModule() {
|
if entry.IsSubModule() {
|
||||||
subModuleURL := ""
|
commitsInfo[i].SubmoduleFile, err = GetCommitInfoSubmoduleFile(repoLink, path.Join(treePath, entry.Name()), commit, entry.ID)
|
||||||
var fullPath string
|
if err != nil {
|
||||||
if len(treePath) > 0 {
|
|
||||||
fullPath = treePath + "/" + entry.Name()
|
|
||||||
} else {
|
|
||||||
fullPath = entry.Name()
|
|
||||||
}
|
|
||||||
if subModule, err := commit.GetSubModule(fullPath); err != nil {
|
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
} else if subModule != nil {
|
|
||||||
subModuleURL = subModule.URL
|
|
||||||
}
|
}
|
||||||
subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String())
|
|
||||||
commitsInfo[i].SubmoduleFile = subModuleFile
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -82,7 +83,7 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Context.TODO() - if graceful has started we should use its Shutdown context otherwise use install signals in TestMain.
|
// FIXME: Context.TODO() - if graceful has started we should use its Shutdown context otherwise use install signals in TestMain.
|
||||||
commitsInfo, treeCommit, err := entries.GetCommitsInfo(t.Context(), commit, testCase.Path)
|
commitsInfo, treeCommit, err := entries.GetCommitsInfo(t.Context(), "/any/repo-link", commit, testCase.Path)
|
||||||
assert.NoError(t, err, "Unable to get commit information for entries of subtree: %s in commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
|
assert.NoError(t, err, "Unable to get commit information for entries of subtree: %s in commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
@@ -120,6 +121,23 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
|
|||||||
defer clonedRepo1.Close()
|
defer clonedRepo1.Close()
|
||||||
|
|
||||||
testGetCommitsInfo(t, clonedRepo1)
|
testGetCommitsInfo(t, clonedRepo1)
|
||||||
|
|
||||||
|
t.Run("NonExistingSubmoduleAsNil", func(t *testing.T) {
|
||||||
|
commit, err := bareRepo1.GetCommit("HEAD")
|
||||||
|
require.NoError(t, err)
|
||||||
|
treeEntry, err := commit.GetTreeEntryByPath("file1.txt")
|
||||||
|
require.NoError(t, err)
|
||||||
|
cisf, err := GetCommitInfoSubmoduleFile("/any/repo-link", "file1.txt", commit, treeEntry.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, &CommitSubmoduleFile{
|
||||||
|
repoLink: "/any/repo-link",
|
||||||
|
fullPath: "file1.txt",
|
||||||
|
refURL: "",
|
||||||
|
refID: "e2129701f1a4d54dc44f03c93bca0a2aec7c5449",
|
||||||
|
}, cisf)
|
||||||
|
// since there is no refURL, it means that the submodule info doesn't exist, so it won't have a web link
|
||||||
|
assert.Nil(t, cisf.SubmoduleWebLinkTree(t.Context()))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
|
func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
|
||||||
@@ -159,7 +177,7 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
|
|||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.Run(benchmark.name, func(b *testing.B) {
|
b.Run(benchmark.name, func(b *testing.B) {
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
_, _, err := entries.GetCommitsInfo(b.Context(), commit, "")
|
_, _, err := entries.GetCommitsInfo(b.Context(), "/any/repo-link", commit, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) {
|
|||||||
return c.submoduleCache, nil
|
return c.submoduleCache, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSubModule get the submodule according entry name
|
// GetSubModule gets the submodule by the entry name.
|
||||||
|
// It returns "nil, nil" if the submodule does not exist, caller should always remember to check the "nil"
|
||||||
func (c *Commit) GetSubModule(entryName string) (*SubModule, error) {
|
func (c *Commit) GetSubModule(entryName string) (*SubModule, error) {
|
||||||
modules, err := c.GetSubModules()
|
modules, err := c.GetSubModules()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,49 +6,64 @@ package git
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
giturl "code.gitea.io/gitea/modules/git/url"
|
giturl "code.gitea.io/gitea/modules/git/url"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CommitSubmoduleFile represents a file with submodule type.
|
// CommitSubmoduleFile represents a file with submodule type.
|
||||||
type CommitSubmoduleFile struct {
|
type CommitSubmoduleFile struct {
|
||||||
refURL string
|
|
||||||
parsedURL *giturl.RepositoryURL
|
|
||||||
parsed bool
|
|
||||||
refID string
|
|
||||||
repoLink string
|
repoLink string
|
||||||
|
fullPath string
|
||||||
|
refURL string
|
||||||
|
refID string
|
||||||
|
|
||||||
|
parsed bool
|
||||||
|
parsedTargetLink string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCommitSubmoduleFile create a new submodule file
|
// NewCommitSubmoduleFile create a new submodule file
|
||||||
func NewCommitSubmoduleFile(refURL, refID string) *CommitSubmoduleFile {
|
func NewCommitSubmoduleFile(repoLink, fullPath, refURL, refID string) *CommitSubmoduleFile {
|
||||||
return &CommitSubmoduleFile{refURL: refURL, refID: refID}
|
return &CommitSubmoduleFile{repoLink: repoLink, fullPath: fullPath, refURL: refURL, refID: refID}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RefID returns the commit ID of the submodule, it returns empty string for nil receiver
|
||||||
func (sf *CommitSubmoduleFile) RefID() string {
|
func (sf *CommitSubmoduleFile) RefID() string {
|
||||||
return sf.refID // this function is only used in templates
|
if sf == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return sf.refID
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubmoduleWebLink tries to make some web links for a submodule, it also works on "nil" receiver
|
func (sf *CommitSubmoduleFile) getWebLinkInTargetRepo(ctx context.Context, moreLinkPath string) *SubmoduleWebLink {
|
||||||
func (sf *CommitSubmoduleFile) SubmoduleWebLink(ctx context.Context, optCommitID ...string) *SubmoduleWebLink {
|
if sf == nil || sf.refURL == "" {
|
||||||
if sf == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(sf.refURL, "../") {
|
||||||
|
targetLink := path.Join(sf.repoLink, sf.refURL)
|
||||||
|
return &SubmoduleWebLink{RepoWebLink: targetLink, CommitWebLink: targetLink + moreLinkPath}
|
||||||
|
}
|
||||||
if !sf.parsed {
|
if !sf.parsed {
|
||||||
sf.parsed = true
|
sf.parsed = true
|
||||||
parsedURL, err := giturl.ParseRepositoryURL(ctx, sf.refURL)
|
parsedURL, err := giturl.ParseRepositoryURL(ctx, sf.refURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
sf.parsedURL = parsedURL
|
sf.parsedTargetLink = giturl.MakeRepositoryWebLink(parsedURL)
|
||||||
sf.repoLink = giturl.MakeRepositoryWebLink(sf.parsedURL)
|
|
||||||
}
|
}
|
||||||
var commitLink string
|
return &SubmoduleWebLink{RepoWebLink: sf.parsedTargetLink, CommitWebLink: sf.parsedTargetLink + moreLinkPath}
|
||||||
if len(optCommitID) == 2 {
|
|
||||||
commitLink = sf.repoLink + "/compare/" + optCommitID[0] + "..." + optCommitID[1]
|
|
||||||
} else if len(optCommitID) == 1 {
|
|
||||||
commitLink = sf.repoLink + "/tree/" + optCommitID[0]
|
|
||||||
} else {
|
|
||||||
commitLink = sf.repoLink + "/tree/" + sf.refID
|
|
||||||
}
|
}
|
||||||
return &SubmoduleWebLink{RepoWebLink: sf.repoLink, CommitWebLink: commitLink}
|
|
||||||
|
// SubmoduleWebLinkTree tries to make the submodule's tree link in its own repo, it also works on "nil" receiver
|
||||||
|
// It returns nil if the submodule does not have a valid URL or is nil
|
||||||
|
func (sf *CommitSubmoduleFile) SubmoduleWebLinkTree(ctx context.Context, optCommitID ...string) *SubmoduleWebLink {
|
||||||
|
return sf.getWebLinkInTargetRepo(ctx, "/tree/"+util.OptionalArg(optCommitID, sf.RefID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmoduleWebLinkCompare tries to make the submodule's compare link in its own repo, it also works on "nil" receiver
|
||||||
|
// It returns nil if the submodule does not have a valid URL or is nil
|
||||||
|
func (sf *CommitSubmoduleFile) SubmoduleWebLinkCompare(ctx context.Context, commitID1, commitID2 string) *SubmoduleWebLink {
|
||||||
|
return sf.getWebLinkInTargetRepo(ctx, "/compare/"+commitID1+"..."+commitID2)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,20 +10,31 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestCommitSubmoduleLink(t *testing.T) {
|
func TestCommitSubmoduleLink(t *testing.T) {
|
||||||
sf := NewCommitSubmoduleFile("git@github.com:user/repo.git", "aaaa")
|
assert.Nil(t, (*CommitSubmoduleFile)(nil).SubmoduleWebLinkTree(t.Context()))
|
||||||
|
assert.Nil(t, (*CommitSubmoduleFile)(nil).SubmoduleWebLinkCompare(t.Context(), "", ""))
|
||||||
|
assert.Nil(t, (&CommitSubmoduleFile{}).SubmoduleWebLinkTree(t.Context()))
|
||||||
|
assert.Nil(t, (&CommitSubmoduleFile{}).SubmoduleWebLinkCompare(t.Context(), "", ""))
|
||||||
|
|
||||||
wl := sf.SubmoduleWebLink(t.Context())
|
t.Run("GitHubRepo", func(t *testing.T) {
|
||||||
|
sf := NewCommitSubmoduleFile("/any/repo-link", "full-path", "git@github.com:user/repo.git", "aaaa")
|
||||||
|
wl := sf.SubmoduleWebLinkTree(t.Context())
|
||||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||||
assert.Equal(t, "https://github.com/user/repo/tree/aaaa", wl.CommitWebLink)
|
assert.Equal(t, "https://github.com/user/repo/tree/aaaa", wl.CommitWebLink)
|
||||||
|
|
||||||
wl = sf.SubmoduleWebLink(t.Context(), "1111")
|
wl = sf.SubmoduleWebLinkCompare(t.Context(), "1111", "2222")
|
||||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
|
||||||
assert.Equal(t, "https://github.com/user/repo/tree/1111", wl.CommitWebLink)
|
|
||||||
|
|
||||||
wl = sf.SubmoduleWebLink(t.Context(), "1111", "2222")
|
|
||||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||||
assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink)
|
assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink)
|
||||||
|
})
|
||||||
|
|
||||||
wl = (*CommitSubmoduleFile)(nil).SubmoduleWebLink(t.Context())
|
t.Run("RelativePath", func(t *testing.T) {
|
||||||
assert.Nil(t, wl)
|
sf := NewCommitSubmoduleFile("/subpath/any/repo-home-link", "full-path", "../../user/repo", "aaaa")
|
||||||
|
wl := sf.SubmoduleWebLinkTree(t.Context())
|
||||||
|
assert.Equal(t, "/subpath/user/repo", wl.RepoWebLink)
|
||||||
|
assert.Equal(t, "/subpath/user/repo/tree/aaaa", wl.CommitWebLink)
|
||||||
|
|
||||||
|
sf = NewCommitSubmoduleFile("/subpath/any/repo-home-link", "dir/submodule", "../../user/repo", "aaaa")
|
||||||
|
wl = sf.SubmoduleWebLinkCompare(t.Context(), "1111", "2222")
|
||||||
|
assert.Equal(t, "/subpath/user/repo", wl.RepoWebLink)
|
||||||
|
assert.Equal(t, "/subpath/user/repo/compare/1111...2222", wl.CommitWebLink)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseDiffHunkString parse the diff hunk content and return
|
// ParseDiffHunkString parse the diff hunk content and return
|
||||||
func ParseDiffHunkString(diffhunk string) (leftLine, leftHunk, rightLine, righHunk int) {
|
func ParseDiffHunkString(diffHunk string) (leftLine, leftHunk, rightLine, rightHunk int) {
|
||||||
ss := strings.Split(diffhunk, "@@")
|
ss := strings.Split(diffHunk, "@@")
|
||||||
ranges := strings.Split(ss[1][1:], " ")
|
ranges := strings.Split(ss[1][1:], " ")
|
||||||
leftRange := strings.Split(ranges[0], ",")
|
leftRange := strings.Split(ranges[0], ",")
|
||||||
leftLine, _ = strconv.Atoi(leftRange[0][1:])
|
leftLine, _ = strconv.Atoi(leftRange[0][1:])
|
||||||
@@ -112,14 +112,19 @@ func ParseDiffHunkString(diffhunk string) (leftLine, leftHunk, rightLine, righHu
|
|||||||
rightRange := strings.Split(ranges[1], ",")
|
rightRange := strings.Split(ranges[1], ",")
|
||||||
rightLine, _ = strconv.Atoi(rightRange[0])
|
rightLine, _ = strconv.Atoi(rightRange[0])
|
||||||
if len(rightRange) > 1 {
|
if len(rightRange) > 1 {
|
||||||
righHunk, _ = strconv.Atoi(rightRange[1])
|
rightHunk, _ = strconv.Atoi(rightRange[1])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Debug("Parse line number failed: %v", diffhunk)
|
log.Debug("Parse line number failed: %v", diffHunk)
|
||||||
rightLine = leftLine
|
rightLine = leftLine
|
||||||
righHunk = leftHunk
|
rightHunk = leftHunk
|
||||||
}
|
}
|
||||||
return leftLine, leftHunk, rightLine, righHunk
|
if rightLine == 0 {
|
||||||
|
// FIXME: GIT-DIFF-CUT-BUG search this tag to see details
|
||||||
|
// this is only a hacky patch, the rightLine&rightHunk might still be incorrect in some cases.
|
||||||
|
rightLine++
|
||||||
|
}
|
||||||
|
return leftLine, leftHunk, rightLine, rightHunk
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9]
|
// Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9]
|
||||||
@@ -270,6 +275,12 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
|
|||||||
oldNumOfLines++
|
oldNumOfLines++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "git diff" outputs "@@ -1 +1,3 @@" for "OLD" => "A\nB\nC"
|
||||||
|
// FIXME: GIT-DIFF-CUT-BUG But there is a bug in CutDiffAroundLine, then the "Patch" stored in the comment model becomes "@@ -1,1 +0,4 @@"
|
||||||
|
// It may generate incorrect results for difference cases, for example: delete 2 line add 1 line, delete 2 line add 2 line etc, need to double check.
|
||||||
|
// For example: "L1\nL2" => "A\nB", then the patch shows "L2" as line 1 on the left (deleted part)
|
||||||
|
|
||||||
// construct the new hunk header
|
// construct the new hunk header
|
||||||
newHunk[headerLines] = fmt.Sprintf("@@ -%d,%d +%d,%d @@",
|
newHunk[headerLines] = fmt.Sprintf("@@ -%d,%d +%d,%d @@",
|
||||||
oldBegin, oldNumOfLines, newBegin, newNumOfLines)
|
oldBegin, oldNumOfLines, newBegin, newNumOfLines)
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ type Parser struct {
|
|||||||
func NewParser(r io.Reader, format Format) *Parser {
|
func NewParser(r io.Reader, format Format) *Parser {
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
|
|
||||||
|
// default MaxScanTokenSize = 64 kiB may be too small for some references,
|
||||||
|
// so allow the buffer to grow up to 4x if needed
|
||||||
|
scanner.Buffer(nil, 4*bufio.MaxScanTokenSize)
|
||||||
|
|
||||||
// in addition to the reference delimiter we specified in the --format,
|
// in addition to the reference delimiter we specified in the --format,
|
||||||
// `git for-each-ref` will always add a newline after every reference.
|
// `git for-each-ref` will always add a newline after every reference.
|
||||||
refDelim := make([]byte, 0, len(format.refDelim)+1)
|
refDelim := make([]byte, 0, len(format.refDelim)+1)
|
||||||
@@ -70,6 +74,9 @@ func NewParser(r io.Reader, format Format) *Parser {
|
|||||||
// { "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" }
|
// { "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" }
|
||||||
func (p *Parser) Next() map[string]string {
|
func (p *Parser) Next() map[string]string {
|
||||||
if !p.scanner.Scan() {
|
if !p.scanner.Scan() {
|
||||||
|
if err := p.scanner.Err(); err != nil {
|
||||||
|
p.err = err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fields, err := p.parseRef(p.scanner.Text())
|
fields, err := p.parseRef(p.scanner.Text())
|
||||||
|
|||||||
@@ -51,30 +51,16 @@ func GetHook(repoPath, name string) (*Hook, error) {
|
|||||||
name: name,
|
name: name,
|
||||||
path: filepath.Join(repoPath, "hooks", name+".d", name),
|
path: filepath.Join(repoPath, "hooks", name+".d", name),
|
||||||
}
|
}
|
||||||
isFile, err := util.IsFile(h.path)
|
if data, err := os.ReadFile(h.path); err == nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if isFile {
|
|
||||||
data, err := os.ReadFile(h.path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
h.IsActive = true
|
h.IsActive = true
|
||||||
h.Content = string(data)
|
h.Content = string(data)
|
||||||
return h, nil
|
return h, nil
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
samplePath := filepath.Join(repoPath, "hooks", name+".sample")
|
samplePath := filepath.Join(repoPath, "hooks", name+".sample")
|
||||||
isFile, err = util.IsFile(samplePath)
|
if data, err := os.ReadFile(samplePath); err == nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if isFile {
|
|
||||||
data, err := os.ReadFile(samplePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
h.Sample = string(data)
|
h.Sample = string(data)
|
||||||
}
|
}
|
||||||
return h, nil
|
return h, nil
|
||||||
|
|||||||
@@ -30,6 +30,31 @@ func (e EntryMode) String() string {
|
|||||||
return strconv.FormatInt(int64(e), 8)
|
return strconv.FormatInt(int64(e), 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSubModule if the entry is a sub module
|
||||||
|
func (e EntryMode) IsSubModule() bool {
|
||||||
|
return e == EntryModeCommit
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir if the entry is a sub dir
|
||||||
|
func (e EntryMode) IsDir() bool {
|
||||||
|
return e == EntryModeTree
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLink if the entry is a symlink
|
||||||
|
func (e EntryMode) IsLink() bool {
|
||||||
|
return e == EntryModeSymlink
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRegular if the entry is a regular file
|
||||||
|
func (e EntryMode) IsRegular() bool {
|
||||||
|
return e == EntryModeBlob
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExecutable if the entry is an executable file (not necessarily binary)
|
||||||
|
func (e EntryMode) IsExecutable() bool {
|
||||||
|
return e == EntryModeExec
|
||||||
|
}
|
||||||
|
|
||||||
func ParseEntryMode(mode string) (EntryMode, error) {
|
func ParseEntryMode(mode string) (EntryMode, error) {
|
||||||
switch mode {
|
switch mode {
|
||||||
case "000000":
|
case "000000":
|
||||||
|
|||||||
@@ -59,27 +59,27 @@ func (te *TreeEntry) Size() int64 {
|
|||||||
|
|
||||||
// IsSubModule if the entry is a sub module
|
// IsSubModule if the entry is a sub module
|
||||||
func (te *TreeEntry) IsSubModule() bool {
|
func (te *TreeEntry) IsSubModule() bool {
|
||||||
return te.entryMode == EntryModeCommit
|
return te.entryMode.IsSubModule()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDir if the entry is a sub dir
|
// IsDir if the entry is a sub dir
|
||||||
func (te *TreeEntry) IsDir() bool {
|
func (te *TreeEntry) IsDir() bool {
|
||||||
return te.entryMode == EntryModeTree
|
return te.entryMode.IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsLink if the entry is a symlink
|
// IsLink if the entry is a symlink
|
||||||
func (te *TreeEntry) IsLink() bool {
|
func (te *TreeEntry) IsLink() bool {
|
||||||
return te.entryMode == EntryModeSymlink
|
return te.entryMode.IsLink()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRegular if the entry is a regular file
|
// IsRegular if the entry is a regular file
|
||||||
func (te *TreeEntry) IsRegular() bool {
|
func (te *TreeEntry) IsRegular() bool {
|
||||||
return te.entryMode == EntryModeBlob
|
return te.entryMode.IsRegular()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsExecutable if the entry is an executable file (not necessarily binary)
|
// IsExecutable if the entry is an executable file (not necessarily binary)
|
||||||
func (te *TreeEntry) IsExecutable() bool {
|
func (te *TreeEntry) IsExecutable() bool {
|
||||||
return te.entryMode == EntryModeExec
|
return te.entryMode.IsExecutable()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blob returns the blob object the entry
|
// Blob returns the blob object the entry
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ func TestParseGitURLs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kase: "git@[fe80:14fc:cec5:c174:d88%2510]:go-gitea/gitea.git",
|
kase: "git@[fe80::14fc:cec5:c174:d88%2510]:go-gitea/gitea.git",
|
||||||
expected: &GitURL{
|
expected: &GitURL{
|
||||||
URL: &url.URL{
|
URL: &url.URL{
|
||||||
Scheme: "ssh",
|
Scheme: "ssh",
|
||||||
User: url.User("git"),
|
User: url.User("git"),
|
||||||
Host: "[fe80:14fc:cec5:c174:d88%10]",
|
Host: "[fe80::14fc:cec5:c174:d88%10]",
|
||||||
Path: "go-gitea/gitea.git",
|
Path: "go-gitea/gitea.git",
|
||||||
},
|
},
|
||||||
extraMark: 1,
|
extraMark: 1,
|
||||||
@@ -137,11 +137,11 @@ func TestParseGitURLs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kase: "https://[fe80:14fc:cec5:c174:d88%2510]:20/go-gitea/gitea.git",
|
kase: "https://[fe80::14fc:cec5:c174:d88%2510]:20/go-gitea/gitea.git",
|
||||||
expected: &GitURL{
|
expected: &GitURL{
|
||||||
URL: &url.URL{
|
URL: &url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
Host: "[fe80:14fc:cec5:c174:d88%10]:20",
|
Host: "[fe80::14fc:cec5:c174:d88%10]:20",
|
||||||
Path: "/go-gitea/gitea.git",
|
Path: "/go-gitea/gitea.git",
|
||||||
},
|
},
|
||||||
extraMark: 0,
|
extraMark: 0,
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ObjectCache provides thread-safe cache operations.
|
// ObjectCache provides thread-safe cache operations.
|
||||||
@@ -106,3 +108,16 @@ func HashFilePathForWebUI(s string) string {
|
|||||||
_, _ = h.Write([]byte(s))
|
_, _ = h.Write([]byte(s))
|
||||||
return hex.EncodeToString(h.Sum(nil))
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SplitCommitTitleBody(commitMessage string, titleRuneLimit int) (title, body string) {
|
||||||
|
title, body, _ = strings.Cut(commitMessage, "\n")
|
||||||
|
title, title2 := util.EllipsisTruncateRunes(title, titleRuneLimit)
|
||||||
|
if title2 != "" {
|
||||||
|
if body == "" {
|
||||||
|
body = title2
|
||||||
|
} else {
|
||||||
|
body = title2 + "\n" + body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return title, body
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,3 +15,17 @@ func TestHashFilePathForWebUI(t *testing.T) {
|
|||||||
HashFilePathForWebUI("foobar"),
|
HashFilePathForWebUI("foobar"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSplitCommitTitleBody(t *testing.T) {
|
||||||
|
title, body := SplitCommitTitleBody("啊bcdefg", 4)
|
||||||
|
assert.Equal(t, "啊…", title)
|
||||||
|
assert.Equal(t, "…bcdefg", body)
|
||||||
|
|
||||||
|
title, body = SplitCommitTitleBody("abcdefg\n1234567", 4)
|
||||||
|
assert.Equal(t, "a…", title)
|
||||||
|
assert.Equal(t, "…bcdefg\n1234567", body)
|
||||||
|
|
||||||
|
title, body = SplitCommitTitleBody("abcdefg\n1234567", 100)
|
||||||
|
assert.Equal(t, "abcdefg", title)
|
||||||
|
assert.Equal(t, "1234567", body)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
package hcaptcha
|
package hcaptcha
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -21,6 +24,33 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockTransport struct{}
|
||||||
|
|
||||||
|
func (mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if req.URL.String() != verifyURL {
|
||||||
|
return nil, errors.New("unsupported url")
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyValues, err := url.ParseQuery(string(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseText string
|
||||||
|
if bodyValues.Get("response") == dummyToken {
|
||||||
|
responseText = `{"success":true,"credit":false,"hostname":"dummy-key-pass","challenge_ts":"2025-10-08T16:02:56.136Z"}`
|
||||||
|
} else {
|
||||||
|
responseText = `{"success":false,"error-codes":["invalid-input-response"]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return &http.Response{Request: req, Body: io.NopCloser(strings.NewReader(responseText))}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestCaptcha(t *testing.T) {
|
func TestCaptcha(t *testing.T) {
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
Name string
|
Name string
|
||||||
@@ -55,6 +85,7 @@ func TestCaptcha(t *testing.T) {
|
|||||||
t.Run(tc.Name, func(t *testing.T) {
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
client, err := New(tc.Secret, WithHTTP(&http.Client{
|
client, err := New(tc.Secret, WithHTTP(&http.Client{
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 5,
|
||||||
|
Transport: mockTransport{},
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The only error that can be returned from creating a client
|
// The only error that can be returned from creating a client
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
"code.gitea.io/gitea/modules/queue"
|
"code.gitea.io/gitea/modules/queue"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -166,12 +167,12 @@ func Init() {
|
|||||||
log.Fatal("PID: %d Unable to initialize the bleve Repository Indexer at path: %s Error: %v", os.Getpid(), setting.Indexer.RepoPath, err)
|
log.Fatal("PID: %d Unable to initialize the bleve Repository Indexer at path: %s Error: %v", os.Getpid(), setting.Indexer.RepoPath, err)
|
||||||
}
|
}
|
||||||
case "elasticsearch":
|
case "elasticsearch":
|
||||||
log.Info("PID: %d Initializing Repository Indexer at: %s", os.Getpid(), setting.Indexer.RepoConnStr)
|
log.Info("PID: %d Initializing Repository Indexer at: %s", os.Getpid(), util.SanitizeCredentialURLs(setting.Indexer.RepoConnStr))
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2))
|
log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2))
|
||||||
log.Error("The indexer files are likely corrupted and may need to be deleted")
|
log.Error("The indexer files are likely corrupted and may need to be deleted")
|
||||||
log.Error("You can completely remove the \"%s\" index to make Gitea recreate the indexes", setting.Indexer.RepoConnStr)
|
log.Error("You can completely remove the \"%s\" index to make Gitea recreate the indexes", util.SanitizeCredentialURLs(setting.Indexer.RepoConnStr))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -181,7 +182,7 @@ func Init() {
|
|||||||
cancel()
|
cancel()
|
||||||
(*globalIndexer.Load()).Close()
|
(*globalIndexer.Load()).Close()
|
||||||
close(waitChannel)
|
close(waitChannel)
|
||||||
log.Fatal("PID: %d Unable to initialize the elasticsearch Repository Indexer connstr: %s Error: %v", os.Getpid(), setting.Indexer.RepoConnStr, err)
|
log.Fatal("PID: %d Unable to initialize the elasticsearch Repository Indexer connstr: %s Error: %v", os.Getpid(), util.SanitizeCredentialURLs(setting.Indexer.RepoConnStr), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
"code.gitea.io/gitea/modules/queue"
|
"code.gitea.io/gitea/modules/queue"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IndexerMetadata is used to send data to the queue, so it contains only the ids.
|
// IndexerMetadata is used to send data to the queue, so it contains only the ids.
|
||||||
@@ -100,7 +101,7 @@ func InitIssueIndexer(syncReindex bool) {
|
|||||||
issueIndexer = elasticsearch.NewIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueIndexerName)
|
issueIndexer = elasticsearch.NewIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueIndexerName)
|
||||||
existed, err = issueIndexer.Init(ctx)
|
existed, err = issueIndexer.Init(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", setting.Indexer.IssueConnStr, err)
|
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", util.SanitizeCredentialURLs(setting.Indexer.IssueConnStr), err)
|
||||||
}
|
}
|
||||||
case "db":
|
case "db":
|
||||||
issueIndexer = db.GetIndexer()
|
issueIndexer = db.GetIndexer()
|
||||||
@@ -108,7 +109,7 @@ func InitIssueIndexer(syncReindex bool) {
|
|||||||
issueIndexer = meilisearch.NewIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueConnAuth, setting.Indexer.IssueIndexerName)
|
issueIndexer = meilisearch.NewIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueConnAuth, setting.Indexer.IssueIndexerName)
|
||||||
existed, err = issueIndexer.Init(ctx)
|
existed, err = issueIndexer.Init(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", setting.Indexer.IssueConnStr, err)
|
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", util.SanitizeCredentialURLs(setting.Indexer.IssueConnStr), err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Fatal("Unknown issue indexer type: %s", setting.Indexer.IssueType)
|
log.Fatal("Unknown issue indexer type: %s", setting.Indexer.IssueType)
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ func newInternalRequestLFS(ctx context.Context, internalURL, method string, head
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
req := private.NewInternalRequest(ctx, internalURL, method)
|
req := private.NewInternalRequest(ctx, internalURL, method)
|
||||||
|
req.SetReadWriteTimeout(0)
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
req.Header(k, v)
|
req.Header(k, v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ func FromPtr[T any](v *T) Option[T] {
|
|||||||
return Some(*v)
|
return Some(*v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FromMapLookup[K comparable, V any](m map[K]V, k K) Option[V] {
|
||||||
|
if v, ok := m[k]; ok {
|
||||||
|
return Some(v)
|
||||||
|
}
|
||||||
|
return None[V]()
|
||||||
|
}
|
||||||
|
|
||||||
func FromNonDefault[T comparable](v T) Option[T] {
|
func FromNonDefault[T comparable](v T) Option[T] {
|
||||||
var zero T
|
var zero T
|
||||||
if v == zero {
|
if v == zero {
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ func TestOption(t *testing.T) {
|
|||||||
opt3 := optional.FromNonDefault(1)
|
opt3 := optional.FromNonDefault(1)
|
||||||
assert.True(t, opt3.Has())
|
assert.True(t, opt3.Has())
|
||||||
assert.Equal(t, int(1), opt3.Value())
|
assert.Equal(t, int(1), opt3.Value())
|
||||||
|
|
||||||
|
opt4 := optional.FromMapLookup(map[string]int{"a": 1}, "a")
|
||||||
|
assert.True(t, opt4.Has())
|
||||||
|
assert.Equal(t, 1, opt4.Value())
|
||||||
|
opt4 = optional.FromMapLookup(map[string]int{"a": 1}, "b")
|
||||||
|
assert.False(t, opt4.Has())
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ParseBool(t *testing.T) {
|
func Test_ParseBool(t *testing.T) {
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ type ProgrammingLanguage struct {
|
|||||||
// https://schema.org/Person
|
// https://schema.org/Person
|
||||||
type Person struct {
|
type Person struct {
|
||||||
Type string `json:"@type,omitempty"`
|
Type string `json:"@type,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"` // inherited from https://schema.org/Thing
|
||||||
GivenName string `json:"givenName,omitempty"`
|
GivenName string `json:"givenName,omitempty"`
|
||||||
MiddleName string `json:"middleName,omitempty"`
|
MiddleName string `json:"middleName,omitempty"`
|
||||||
FamilyName string `json:"familyName,omitempty"`
|
FamilyName string `json:"familyName,omitempty"`
|
||||||
@@ -184,11 +185,17 @@ func ParsePackage(sr io.ReaderAt, size int64, mr io.Reader) (*Package, error) {
|
|||||||
p.Metadata.Description = ssc.Description
|
p.Metadata.Description = ssc.Description
|
||||||
p.Metadata.Keywords = ssc.Keywords
|
p.Metadata.Keywords = ssc.Keywords
|
||||||
p.Metadata.License = ssc.License
|
p.Metadata.License = ssc.License
|
||||||
p.Metadata.Author = Person{
|
author := Person{
|
||||||
|
Name: ssc.Author.Name,
|
||||||
GivenName: ssc.Author.GivenName,
|
GivenName: ssc.Author.GivenName,
|
||||||
MiddleName: ssc.Author.MiddleName,
|
MiddleName: ssc.Author.MiddleName,
|
||||||
FamilyName: ssc.Author.FamilyName,
|
FamilyName: ssc.Author.FamilyName,
|
||||||
}
|
}
|
||||||
|
// If Name is not provided, generate it from individual name components
|
||||||
|
if author.Name == "" {
|
||||||
|
author.Name = author.String()
|
||||||
|
}
|
||||||
|
p.Metadata.Author = author
|
||||||
|
|
||||||
p.Metadata.RepositoryURL = ssc.CodeRepository
|
p.Metadata.RepositoryURL = ssc.CodeRepository
|
||||||
if !validation.IsValidURL(p.Metadata.RepositoryURL) {
|
if !validation.IsValidURL(p.Metadata.RepositoryURL) {
|
||||||
|
|||||||
@@ -97,10 +97,49 @@ func TestParsePackage(t *testing.T) {
|
|||||||
assert.Equal(t, packageDescription, p.Metadata.Description)
|
assert.Equal(t, packageDescription, p.Metadata.Description)
|
||||||
assert.ElementsMatch(t, []string{"swift", "package"}, p.Metadata.Keywords)
|
assert.ElementsMatch(t, []string{"swift", "package"}, p.Metadata.Keywords)
|
||||||
assert.Equal(t, packageLicense, p.Metadata.License)
|
assert.Equal(t, packageLicense, p.Metadata.License)
|
||||||
|
assert.Equal(t, packageAuthor, p.Metadata.Author.Name)
|
||||||
assert.Equal(t, packageAuthor, p.Metadata.Author.GivenName)
|
assert.Equal(t, packageAuthor, p.Metadata.Author.GivenName)
|
||||||
assert.Equal(t, packageRepositoryURL, p.Metadata.RepositoryURL)
|
assert.Equal(t, packageRepositoryURL, p.Metadata.RepositoryURL)
|
||||||
assert.ElementsMatch(t, []string{packageRepositoryURL}, p.RepositoryURLs)
|
assert.ElementsMatch(t, []string{packageRepositoryURL}, p.RepositoryURLs)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("WithExplicitNameField", func(t *testing.T) {
|
||||||
|
data := createArchive(map[string][]byte{
|
||||||
|
"Package.swift": []byte("// swift-tools-version:5.7\n//\n// Package.swift"),
|
||||||
|
})
|
||||||
|
|
||||||
|
authorName := "John Doe"
|
||||||
|
p, err := ParsePackage(
|
||||||
|
data,
|
||||||
|
data.Size(),
|
||||||
|
strings.NewReader(`{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","author":{"name":"`+authorName+`","givenName":"John","familyName":"Doe"}}`),
|
||||||
|
)
|
||||||
|
assert.NotNil(t, p)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, authorName, p.Metadata.Author.Name)
|
||||||
|
assert.Equal(t, "John", p.Metadata.Author.GivenName)
|
||||||
|
assert.Equal(t, "Doe", p.Metadata.Author.FamilyName)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NameFieldGeneration", func(t *testing.T) {
|
||||||
|
data := createArchive(map[string][]byte{
|
||||||
|
"Package.swift": []byte("// swift-tools-version:5.7\n//\n// Package.swift"),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test with only individual name components - Name should be auto-generated
|
||||||
|
p, err := ParsePackage(
|
||||||
|
data,
|
||||||
|
data.Size(),
|
||||||
|
strings.NewReader(`{"author":{"givenName":"John","middleName":"Q","familyName":"Doe"}}`),
|
||||||
|
)
|
||||||
|
assert.NotNil(t, p)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "John Q Doe", p.Metadata.Author.Name)
|
||||||
|
assert.Equal(t, "John", p.Metadata.Author.GivenName)
|
||||||
|
assert.Equal(t, "Q", p.Metadata.Author.MiddleName)
|
||||||
|
assert.Equal(t, "Doe", p.Metadata.Author.FamilyName)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrimmedVersionString(t *testing.T) {
|
func TestTrimmedVersionString(t *testing.T) {
|
||||||
@@ -142,3 +181,43 @@ func TestTrimmedVersionString(t *testing.T) {
|
|||||||
assert.Equal(t, c.Expected, TrimmedVersionString(c.Version))
|
assert.Equal(t, c.Expected, TrimmedVersionString(c.Version))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPersonNameString(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Name string
|
||||||
|
Person Person
|
||||||
|
Expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "GivenNameOnly",
|
||||||
|
Person: Person{GivenName: "John"},
|
||||||
|
Expected: "John",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "GivenAndFamily",
|
||||||
|
Person: Person{GivenName: "John", FamilyName: "Doe"},
|
||||||
|
Expected: "John Doe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "FullName",
|
||||||
|
Person: Person{GivenName: "John", MiddleName: "Q", FamilyName: "Doe"},
|
||||||
|
Expected: "John Q Doe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "MiddleAndFamily",
|
||||||
|
Person: Person{MiddleName: "Q", FamilyName: "Doe"},
|
||||||
|
Expected: "Q Doe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Empty",
|
||||||
|
Person: Person{},
|
||||||
|
Expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.Name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, c.Expected, c.Person.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,11 +41,14 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("GetObjectFormat: %w", err)
|
return 0, fmt.Errorf("GetObjectFormat: %w", err)
|
||||||
}
|
}
|
||||||
_, err = db.GetEngine(ctx).ID(repo.ID).Update(&repo_model.Repository{ObjectFormatName: objFmt.Name()})
|
|
||||||
|
if repo.ObjectFormatName != objFmt.Name() {
|
||||||
|
repo.ObjectFormatName = objFmt.Name()
|
||||||
|
_, err = db.GetEngine(ctx).ID(repo.ID).NoAutoTime().Update(&repo_model.Repository{ObjectFormatName: objFmt.Name()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("UpdateRepository: %w", err)
|
return 0, fmt.Errorf("UpdateRepository: %w", err)
|
||||||
}
|
}
|
||||||
repo.ObjectFormatName = objFmt.Name() // keep consistent with db
|
}
|
||||||
|
|
||||||
allBranches := container.Set[string]{}
|
allBranches := container.Set[string]{}
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ var (
|
|||||||
ZombieTaskTimeout time.Duration `ini:"ZOMBIE_TASK_TIMEOUT"`
|
ZombieTaskTimeout time.Duration `ini:"ZOMBIE_TASK_TIMEOUT"`
|
||||||
EndlessTaskTimeout time.Duration `ini:"ENDLESS_TASK_TIMEOUT"`
|
EndlessTaskTimeout time.Duration `ini:"ENDLESS_TASK_TIMEOUT"`
|
||||||
AbandonedJobTimeout time.Duration `ini:"ABANDONED_JOB_TIMEOUT"`
|
AbandonedJobTimeout time.Duration `ini:"ABANDONED_JOB_TIMEOUT"`
|
||||||
SkipWorkflowStrings []string `ìni:"SKIP_WORKFLOW_STRINGS"`
|
SkipWorkflowStrings []string `ini:"SKIP_WORKFLOW_STRINGS"`
|
||||||
}{
|
}{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
DefaultActionsURL: defaultActionsURLGitHub,
|
DefaultActionsURL: defaultActionsURLGitHub,
|
||||||
|
|||||||
@@ -202,11 +202,11 @@ func NewConfigProviderFromFile(file string) (ConfigProvider, error) {
|
|||||||
loadedFromEmpty := true
|
loadedFromEmpty := true
|
||||||
|
|
||||||
if file != "" {
|
if file != "" {
|
||||||
isFile, err := util.IsFile(file)
|
isExist, err := util.IsExist(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to check if %q is a file. Error: %v", file, err)
|
return nil, fmt.Errorf("unable to check if %q exists: %v", file, err)
|
||||||
}
|
}
|
||||||
if isFile {
|
if isExist {
|
||||||
if err = cfg.Append(file); err != nil {
|
if err = cfg.Append(file); err != nil {
|
||||||
return nil, fmt.Errorf("failed to load config file %q: %v", file, err)
|
return nil, fmt.Errorf("failed to load config file %q: %v", file, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,3 +41,56 @@ EXTEND = true
|
|||||||
assert.Equal(t, "white rabbit", extended.Second)
|
assert.Equal(t, "white rabbit", extended.Second)
|
||||||
assert.True(t, extended.Extend)
|
assert.True(t, extended.Extend)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test_getCronSettings2 tests that getCronSettings can not handle two levels of embedding
|
||||||
|
func Test_getCronSettings2(t *testing.T) {
|
||||||
|
type BaseStruct struct {
|
||||||
|
Enabled bool
|
||||||
|
RunAtStart bool
|
||||||
|
Schedule string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Extended struct {
|
||||||
|
BaseStruct
|
||||||
|
Extend bool
|
||||||
|
}
|
||||||
|
type Extended2 struct {
|
||||||
|
Extended
|
||||||
|
Third string
|
||||||
|
}
|
||||||
|
|
||||||
|
iniStr := `
|
||||||
|
[cron.test]
|
||||||
|
ENABLED = TRUE
|
||||||
|
RUN_AT_START = TRUE
|
||||||
|
SCHEDULE = @every 1h
|
||||||
|
EXTEND = true
|
||||||
|
THIRD = white rabbit
|
||||||
|
`
|
||||||
|
cfg, err := NewConfigProviderFromData(iniStr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
extended := &Extended2{
|
||||||
|
Extended: Extended{
|
||||||
|
BaseStruct: BaseStruct{
|
||||||
|
Enabled: false,
|
||||||
|
RunAtStart: false,
|
||||||
|
Schedule: "@every 72h",
|
||||||
|
},
|
||||||
|
Extend: false,
|
||||||
|
},
|
||||||
|
Third: "black rabbit",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = getCronSettings(cfg, "test", extended)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// This confirms the first level of embedding works
|
||||||
|
assert.Equal(t, "white rabbit", extended.Third)
|
||||||
|
assert.True(t, extended.Extend)
|
||||||
|
|
||||||
|
// This confirms 2 levels of embedding doesn't work
|
||||||
|
assert.False(t, extended.Enabled)
|
||||||
|
assert.False(t, extended.RunAtStart)
|
||||||
|
assert.Equal(t, "@every 72h", extended.Schedule)
|
||||||
|
}
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
|||||||
HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr)
|
HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Fatal("Invalid PROTOCOL %q", Protocol)
|
log.Fatal("Invalid PROTOCOL %q", protocolCfg)
|
||||||
}
|
}
|
||||||
UseProxyProtocol = sec.Key("USE_PROXY_PROTOCOL").MustBool(false)
|
UseProxyProtocol = sec.Key("USE_PROXY_PROTOCOL").MustBool(false)
|
||||||
ProxyProtocolTLSBridging = sec.Key("PROXY_PROTOCOL_TLS_BRIDGING").MustBool(false)
|
ProxyProtocolTLSBridging = sec.Key("PROXY_PROTOCOL_TLS_BRIDGING").MustBool(false)
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ type Repository struct {
|
|||||||
Private bool `json:"private"`
|
Private bool `json:"private"`
|
||||||
Fork bool `json:"fork"`
|
Fork bool `json:"fork"`
|
||||||
Template bool `json:"template"`
|
Template bool `json:"template"`
|
||||||
Parent *Repository `json:"parent"`
|
Parent *Repository `json:"parent,omitempty"`
|
||||||
Mirror bool `json:"mirror"`
|
Mirror bool `json:"mirror"`
|
||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
@@ -112,7 +112,7 @@ type Repository struct {
|
|||||||
ObjectFormatName string `json:"object_format_name"`
|
ObjectFormatName string `json:"object_format_name"`
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
|
MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
|
||||||
RepoTransfer *RepoTransfer `json:"repo_transfer"`
|
RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"`
|
||||||
Topics []string `json:"topics"`
|
Topics []string `json:"topics"`
|
||||||
Licenses []string `json:"licenses"`
|
Licenses []string `json:"licenses"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,6 @@ func TestCountFmt(t *testing.T) {
|
|||||||
assert.Equal(t, "125", countFmt(125))
|
assert.Equal(t, "125", countFmt(125))
|
||||||
assert.Equal(t, "1.3k", countFmt(int64(1317)))
|
assert.Equal(t, "1.3k", countFmt(int64(1317)))
|
||||||
assert.Equal(t, "21.3M", countFmt(21317675))
|
assert.Equal(t, "21.3M", countFmt(21317675))
|
||||||
assert.Equal(t, "45.7G", countFmt(45721317675))
|
assert.Equal(t, "45.7G", countFmt(int64(45721317675)))
|
||||||
assert.Empty(t, countFmt("test"))
|
assert.Empty(t, countFmt("test"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,15 +115,10 @@ func IsDir(dir string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsFile returns true if given path is a file,
|
func IsRegularFile(filePath string) (bool, error) {
|
||||||
// or returns false when it's a directory or does not exist.
|
f, err := os.Lstat(filePath)
|
||||||
func IsFile(filePath string) (bool, error) {
|
|
||||||
f, err := os.Stat(filePath)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return !f.IsDir(), nil
|
return f.Mode().IsRegular(), nil
|
||||||
}
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func IsLikelyEllipsisLeftPart(s string) bool {
|
|||||||
return strings.HasSuffix(s, utf8Ellipsis) || strings.HasSuffix(s, asciiEllipsis)
|
return strings.HasSuffix(s, utf8Ellipsis) || strings.HasSuffix(s, asciiEllipsis)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ellipsisGuessDisplayWidth(r rune) int {
|
func ellipsisDisplayGuessWidth(r rune) int {
|
||||||
// To make the truncated string as long as possible,
|
// To make the truncated string as long as possible,
|
||||||
// CJK/emoji chars are considered as 2-ASCII width but not 3-4 bytes width.
|
// CJK/emoji chars are considered as 2-ASCII width but not 3-4 bytes width.
|
||||||
// Here we only make the best guess (better than counting them in bytes),
|
// Here we only make the best guess (better than counting them in bytes),
|
||||||
@@ -48,13 +48,17 @@ func ellipsisGuessDisplayWidth(r rune) int {
|
|||||||
// It appends "…" or "..." at the end of truncated string.
|
// It appends "…" or "..." at the end of truncated string.
|
||||||
// It guarantees the length of the returned runes doesn't exceed the limit.
|
// It guarantees the length of the returned runes doesn't exceed the limit.
|
||||||
func EllipsisDisplayString(str string, limit int) string {
|
func EllipsisDisplayString(str string, limit int) string {
|
||||||
s, _, _, _ := ellipsisDisplayString(str, limit)
|
s, _, _, _ := ellipsisDisplayString(str, limit, ellipsisDisplayGuessWidth)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// EllipsisDisplayStringX works like EllipsisDisplayString while it also returns the right part
|
// EllipsisDisplayStringX works like EllipsisDisplayString while it also returns the right part
|
||||||
func EllipsisDisplayStringX(str string, limit int) (left, right string) {
|
func EllipsisDisplayStringX(str string, limit int) (left, right string) {
|
||||||
left, offset, truncated, encounterInvalid := ellipsisDisplayString(str, limit)
|
return ellipsisDisplayStringX(str, limit, ellipsisDisplayGuessWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ellipsisDisplayStringX(str string, limit int, widthGuess func(rune) int) (left, right string) {
|
||||||
|
left, offset, truncated, encounterInvalid := ellipsisDisplayString(str, limit, widthGuess)
|
||||||
if truncated {
|
if truncated {
|
||||||
right = str[offset:]
|
right = str[offset:]
|
||||||
r, _ := utf8.DecodeRune(UnsafeStringToBytes(right))
|
r, _ := utf8.DecodeRune(UnsafeStringToBytes(right))
|
||||||
@@ -68,7 +72,7 @@ func EllipsisDisplayStringX(str string, limit int) (left, right string) {
|
|||||||
return left, right
|
return left, right
|
||||||
}
|
}
|
||||||
|
|
||||||
func ellipsisDisplayString(str string, limit int) (res string, offset int, truncated, encounterInvalid bool) {
|
func ellipsisDisplayString(str string, limit int, widthGuess func(rune) int) (res string, offset int, truncated, encounterInvalid bool) {
|
||||||
if len(str) <= limit {
|
if len(str) <= limit {
|
||||||
return str, len(str), false, false
|
return str, len(str), false, false
|
||||||
}
|
}
|
||||||
@@ -81,7 +85,7 @@ func ellipsisDisplayString(str string, limit int) (res string, offset int, trunc
|
|||||||
for i, r := range str {
|
for i, r := range str {
|
||||||
encounterInvalid = encounterInvalid || r == utf8.RuneError
|
encounterInvalid = encounterInvalid || r == utf8.RuneError
|
||||||
pos = i
|
pos = i
|
||||||
runeWidth := ellipsisGuessDisplayWidth(r)
|
runeWidth := widthGuess(r)
|
||||||
if used+runeWidth+3 > limit {
|
if used+runeWidth+3 > limit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -96,7 +100,7 @@ func ellipsisDisplayString(str string, limit int) (res string, offset int, trunc
|
|||||||
if nextCnt >= 4 {
|
if nextCnt >= 4 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
nextWidth += ellipsisGuessDisplayWidth(r)
|
nextWidth += widthGuess(r)
|
||||||
nextCnt++
|
nextCnt++
|
||||||
}
|
}
|
||||||
if nextCnt <= 3 && used+nextWidth <= limit {
|
if nextCnt <= 3 && used+nextWidth <= limit {
|
||||||
@@ -114,6 +118,10 @@ func ellipsisDisplayString(str string, limit int) (res string, offset int, trunc
|
|||||||
return str[:offset] + ellipsis, offset, true, encounterInvalid
|
return str[:offset] + ellipsis, offset, true, encounterInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EllipsisTruncateRunes(str string, limit int) (left, right string) {
|
||||||
|
return ellipsisDisplayStringX(str, limit, func(r rune) int { return 1 })
|
||||||
|
}
|
||||||
|
|
||||||
// TruncateRunes returns a truncated string with given rune limit,
|
// TruncateRunes returns a truncated string with given rune limit,
|
||||||
// it returns input string if its rune length doesn't exceed the limit.
|
// it returns input string if its rune length doesn't exceed the limit.
|
||||||
func TruncateRunes(str string, limit int) string {
|
func TruncateRunes(str string, limit int) string {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func TestEllipsisGuessDisplayWidth(t *testing.T) {
|
|||||||
t.Run(c.r, func(t *testing.T) {
|
t.Run(c.r, func(t *testing.T) {
|
||||||
w := 0
|
w := 0
|
||||||
for _, r := range c.r {
|
for _, r := range c.r {
|
||||||
w += ellipsisGuessDisplayWidth(r)
|
w += ellipsisDisplayGuessWidth(r)
|
||||||
}
|
}
|
||||||
assert.Equal(t, c.want, w, "hex=% x", []byte(c.r))
|
assert.Equal(t, c.want, w, "hex=% x", []byte(c.r))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1957,7 +1957,7 @@ pulls.cmd_instruction_checkout_title = Checkout
|
|||||||
pulls.cmd_instruction_checkout_desc = From your project repository, check out a new branch and test the changes.
|
pulls.cmd_instruction_checkout_desc = From your project repository, check out a new branch and test the changes.
|
||||||
pulls.cmd_instruction_merge_title = Merge
|
pulls.cmd_instruction_merge_title = Merge
|
||||||
pulls.cmd_instruction_merge_desc = Merge the changes and update on Gitea.
|
pulls.cmd_instruction_merge_desc = Merge the changes and update on Gitea.
|
||||||
pulls.cmd_instruction_merge_warning = Warning: This operation can not merge pull request because "autodetect manual merge" was not enable
|
pulls.cmd_instruction_merge_warning = Warning: This operation cannot merge pull request because "autodetect manual merge" is not enabled.
|
||||||
pulls.clear_merge_message = Clear merge message
|
pulls.clear_merge_message = Clear merge message
|
||||||
pulls.clear_merge_message_hint = Clearing the merge message will only remove the commit message content and keep generated git trailers such as "Co-Authored-By …".
|
pulls.clear_merge_message_hint = Clearing the merge message will only remove the commit message content and keep generated git trailers such as "Co-Authored-By …".
|
||||||
|
|
||||||
@@ -2153,6 +2153,7 @@ settings.collaboration.write = Write
|
|||||||
settings.collaboration.read = Read
|
settings.collaboration.read = Read
|
||||||
settings.collaboration.owner = Owner
|
settings.collaboration.owner = Owner
|
||||||
settings.collaboration.undefined = Undefined
|
settings.collaboration.undefined = Undefined
|
||||||
|
settings.collaboration.per_unit = Unit Permissions
|
||||||
settings.hooks = Webhooks
|
settings.hooks = Webhooks
|
||||||
settings.githooks = Git Hooks
|
settings.githooks = Git Hooks
|
||||||
settings.basic_settings = Basic Settings
|
settings.basic_settings = Basic Settings
|
||||||
|
|||||||
36
package-lock.json
generated
36
package-lock.json
generated
@@ -35,7 +35,7 @@
|
|||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"katex": "0.16.22",
|
"katex": "0.16.22",
|
||||||
"license-checker-webpack-plugin": "0.2.1",
|
"license-checker-webpack-plugin": "0.2.1",
|
||||||
"mermaid": "11.6.0",
|
"mermaid": "11.10.0",
|
||||||
"mini-css-extract-plugin": "2.9.2",
|
"mini-css-extract-plugin": "2.9.2",
|
||||||
"minimatch": "10.0.1",
|
"minimatch": "10.0.1",
|
||||||
"monaco-editor": "0.52.2",
|
"monaco-editor": "0.52.2",
|
||||||
@@ -1540,9 +1540,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mermaid-js/parser": {
|
"node_modules/@mermaid-js/parser": {
|
||||||
"version": "0.4.0",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.2.tgz",
|
||||||
"integrity": "sha512-wla8XOWvQAwuqy+gxiZqY+c7FokraOTHRWMsbB4AgRx9Sy7zKslNyejy7E+a77qHfey5GXw/ik3IXv/NHMJgaA==",
|
"integrity": "sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"langium": "3.3.1"
|
"langium": "3.3.1"
|
||||||
@@ -6154,9 +6154,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
|
||||||
"integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==",
|
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@types/trusted-types": "^2.0.7"
|
"@types/trusted-types": "^2.0.7"
|
||||||
@@ -9249,14 +9249,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mermaid": {
|
"node_modules/mermaid": {
|
||||||
"version": "11.6.0",
|
"version": "11.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.10.0.tgz",
|
||||||
"integrity": "sha512-PE8hGUy1LDlWIHWBP05SFdqUHGmRcCcK4IzpOKPE35eOw+G9zZgcnMpyunJVUEOgb//KBORPjysKndw8bFLuRg==",
|
"integrity": "sha512-oQsFzPBy9xlpnGxUqLbVY8pvknLlsNIJ0NWwi8SUJjhbP1IT0E0o1lfhU4iYV3ubpy+xkzkaOyDUQMn06vQElQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^7.0.4",
|
"@braintree/sanitize-url": "^7.0.4",
|
||||||
"@iconify/utils": "^2.1.33",
|
"@iconify/utils": "^2.1.33",
|
||||||
"@mermaid-js/parser": "^0.4.0",
|
"@mermaid-js/parser": "^0.6.2",
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"cytoscape": "^3.29.3",
|
"cytoscape": "^3.29.3",
|
||||||
"cytoscape-cose-bilkent": "^4.1.0",
|
"cytoscape-cose-bilkent": "^4.1.0",
|
||||||
@@ -9265,11 +9265,11 @@
|
|||||||
"d3-sankey": "^0.12.3",
|
"d3-sankey": "^0.12.3",
|
||||||
"dagre-d3-es": "7.0.11",
|
"dagre-d3-es": "7.0.11",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dompurify": "^3.2.4",
|
"dompurify": "^3.2.5",
|
||||||
"katex": "^0.16.9",
|
"katex": "^0.16.22",
|
||||||
"khroma": "^2.1.0",
|
"khroma": "^2.1.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^15.0.7",
|
"marked": "^16.0.0",
|
||||||
"roughjs": "^4.6.6",
|
"roughjs": "^4.6.6",
|
||||||
"stylis": "^4.3.6",
|
"stylis": "^4.3.6",
|
||||||
"ts-dedent": "^2.2.0",
|
"ts-dedent": "^2.2.0",
|
||||||
@@ -9277,15 +9277,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mermaid/node_modules/marked": {
|
"node_modules/mermaid/node_modules/marked": {
|
||||||
"version": "15.0.7",
|
"version": "16.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/marked/-/marked-16.2.0.tgz",
|
||||||
"integrity": "sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg==",
|
"integrity": "sha512-LbbTuye+0dWRz2TS9KJ7wsnD4KAtpj0MVkWc90XvBa6AslXsT0hTBVH5k32pcSyHH1fst9XEFJunXHktVy0zlg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"marked": "bin/marked.js"
|
"marked": "bin/marked.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/micromark": {
|
"node_modules/micromark": {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"katex": "0.16.22",
|
"katex": "0.16.22",
|
||||||
"license-checker-webpack-plugin": "0.2.1",
|
"license-checker-webpack-plugin": "0.2.1",
|
||||||
"mermaid": "11.6.0",
|
"mermaid": "11.10.0",
|
||||||
"mini-css-extract-plugin": "2.9.2",
|
"mini-css-extract-plugin": "2.9.2",
|
||||||
"minimatch": "10.0.1",
|
"minimatch": "10.0.1",
|
||||||
"monaco-editor": "0.52.2",
|
"monaco-editor": "0.52.2",
|
||||||
|
|||||||
@@ -230,6 +230,7 @@ func PackageVersionMetadata(ctx *context.Context) {
|
|||||||
},
|
},
|
||||||
Author: swift_module.Person{
|
Author: swift_module.Person{
|
||||||
Type: "Person",
|
Type: "Person",
|
||||||
|
Name: metadata.Author.String(),
|
||||||
GivenName: metadata.Author.GivenName,
|
GivenName: metadata.Author.GivenName,
|
||||||
MiddleName: metadata.Author.MiddleName,
|
MiddleName: metadata.Author.MiddleName,
|
||||||
FamilyName: metadata.Author.FamilyName,
|
FamilyName: metadata.Author.FamilyName,
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ func EditUser(ctx *context.APIContext) {
|
|||||||
Description: optional.FromPtr(form.Description),
|
Description: optional.FromPtr(form.Description),
|
||||||
IsActive: optional.FromPtr(form.Active),
|
IsActive: optional.FromPtr(form.Active),
|
||||||
IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin),
|
IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin),
|
||||||
Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]),
|
Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility),
|
||||||
AllowGitHook: optional.FromPtr(form.AllowGitHook),
|
AllowGitHook: optional.FromPtr(form.AllowGitHook),
|
||||||
AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
|
AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
|
||||||
MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation),
|
MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation),
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ func repoAssignment() func(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.Repo.Permission.HasAnyUnitAccess() {
|
if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() {
|
||||||
ctx.APIErrorNotFound()
|
ctx.APIErrorNotFound()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1241,7 +1241,7 @@ func Routes() *web.Router {
|
|||||||
}, reqToken())
|
}, reqToken())
|
||||||
m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile)
|
m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile)
|
||||||
m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS)
|
m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS)
|
||||||
m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
|
m.Methods("HEAD,GET", "/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
|
||||||
m.Combo("/forks").Get(repo.ListForks).
|
m.Combo("/forks").Get(repo.ListForks).
|
||||||
Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
|
Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
|
||||||
m.Post("/merge-upstream", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeCode), bind(api.MergeUpstreamRequest{}), repo.MergeUpstream)
|
m.Post("/merge-upstream", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeCode), bind(api.MergeUpstreamRequest{}), repo.MergeUpstream)
|
||||||
@@ -1445,7 +1445,7 @@ func Routes() *web.Router {
|
|||||||
m.Delete("", repo.DeleteAvatar)
|
m.Delete("", repo.DeleteAvatar)
|
||||||
}, reqAdmin(), reqToken())
|
}, reqAdmin(), reqToken())
|
||||||
|
|
||||||
m.Get("/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive)
|
m.Methods("HEAD,GET", "/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive)
|
||||||
}, repoAssignment(), checkTokenPublicOnly())
|
}, repoAssignment(), checkTokenPublicOnly())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||||
|
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ func Edit(ctx *context.APIContext) {
|
|||||||
Description: optional.Some(form.Description),
|
Description: optional.Some(form.Description),
|
||||||
Website: optional.Some(form.Website),
|
Website: optional.Some(form.Website),
|
||||||
Location: optional.Some(form.Location),
|
Location: optional.Some(form.Location),
|
||||||
Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]),
|
Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility),
|
||||||
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
|
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
|
||||||
}
|
}
|
||||||
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {
|
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {
|
||||||
|
|||||||
@@ -721,8 +721,8 @@ func deleteIssueComment(ctx *context.APIContext) {
|
|||||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||||
ctx.Status(http.StatusForbidden)
|
ctx.Status(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
} else if comment.Type != issues_model.CommentTypeComment {
|
} else if !comment.Type.HasContentSupport() {
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,5 +44,5 @@ type swaggerResponseActionWorkflow struct {
|
|||||||
// swagger:response ActionWorkflowList
|
// swagger:response ActionWorkflowList
|
||||||
type swaggerResponseActionWorkflowList struct {
|
type swaggerResponseActionWorkflowList struct {
|
||||||
// in:body
|
// in:body
|
||||||
Body []api.ActionWorkflow `json:"body"`
|
Body api.ActionWorkflowResponse `json:"body"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func UpdatePublicKeyInRepo(ctx *context.PrivateContext) {
|
|||||||
ctx.PlainText(http.StatusOK, "success")
|
ctx.PlainText(http.StatusOK, "success")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizedPublicKeyByContent searches content as prefix (leak e-mail part)
|
// AuthorizedPublicKeyByContent searches content as prefix (without comment part)
|
||||||
// and returns public key found.
|
// and returns public key found.
|
||||||
func AuthorizedPublicKeyByContent(ctx *context.PrivateContext) {
|
func AuthorizedPublicKeyByContent(ctx *context.PrivateContext) {
|
||||||
content := ctx.FormString("content")
|
content := ctx.FormString("content")
|
||||||
@@ -57,5 +57,14 @@ func AuthorizedPublicKeyByContent(ctx *context.PrivateContext) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.PlainText(http.StatusOK, publicKey.AuthorizedString())
|
|
||||||
|
authorizedString, err := asymkey_model.AuthorizedStringForKey(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||||
|
Err: err.Error(),
|
||||||
|
UserMsg: "invalid public key",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.PlainText(http.StatusOK, authorizedString)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -639,6 +639,7 @@ func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, s
|
|||||||
ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest,
|
ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest,
|
||||||
ErrorDescription: "cannot proceed your request",
|
ErrorDescription: "cannot proceed your request",
|
||||||
})
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
resp, tokenErr := oauth2_provider.NewAccessTokenResponse(ctx, authorizationCode.Grant, serverKey, clientKey)
|
resp, tokenErr := oauth2_provider.NewAccessTokenResponse(ctx, authorizationCode.Grant, serverKey, clientKey)
|
||||||
if tokenErr != nil {
|
if tokenErr != nil {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/repo"
|
"code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
@@ -15,11 +16,15 @@ import (
|
|||||||
|
|
||||||
// ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
|
// ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
|
||||||
func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
|
func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
|
||||||
commits, err := ctx.Repo.Commit.CommitsByRange(0, 10, "")
|
var commits []*git.Commit
|
||||||
|
var err error
|
||||||
|
if ctx.Repo.Commit != nil {
|
||||||
|
commits, err = ctx.Repo.Commit.CommitsByRange(0, 10, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("ShowBranchFeed", err)
|
ctx.ServerError("ShowBranchFeed", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
title := "Latest commits for branch " + ctx.Repo.BranchName
|
title := "Latest commits for branch " + ctx.Repo.BranchName
|
||||||
link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.RefTypeNameSubURL()}
|
link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.RefTypeNameSubURL()}
|
||||||
|
|||||||
@@ -8,11 +8,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RenderBranchFeed render format for branch or file
|
// RenderBranchFeed render format for branch or file
|
||||||
func RenderBranchFeed(ctx *context.Context) {
|
func RenderBranchFeed(ctx *context.Context, feedType string) {
|
||||||
_, showFeedType := GetFeedType(ctx.PathParam("reponame"), ctx.Req)
|
|
||||||
if ctx.Repo.TreePath == "" {
|
if ctx.Repo.TreePath == "" {
|
||||||
ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
|
ShowBranchFeed(ctx, ctx.Repo.Repository, feedType)
|
||||||
} else {
|
} else {
|
||||||
ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
|
ShowFileFeed(ctx, ctx.Repo.Repository, feedType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RenderBranchFeedRSS(ctx *context.Context) {
|
||||||
|
RenderBranchFeed(ctx, "rss")
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderBranchFeedAtom(ctx *context.Context) {
|
||||||
|
RenderBranchFeed(ctx, "atom")
|
||||||
|
}
|
||||||
|
|||||||
@@ -283,7 +283,18 @@ func NewTeam(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: TEAM-UNIT-PERMISSION: this design is not right, when a new unit is added in the future,
|
// FIXME: TEAM-UNIT-PERMISSION: this design is not right, when a new unit is added in the future,
|
||||||
// admin team won't inherit the correct admin permission for the new unit.
|
// The existing teams won't inherit the correct admin permission for the new unit.
|
||||||
|
// The full history is like this:
|
||||||
|
// 1. There was only "team", no "team unit", so "team.authorize" was used to determine the team permission.
|
||||||
|
// 2. Later, "team unit" was introduced, then the usage of "team.authorize" became inconsistent, and causes various bugs.
|
||||||
|
// - Sometimes, "team.authorize" is used to determine the team permission, e.g. admin, owner
|
||||||
|
// - Sometimes, "team unit" is used not really used and "team unit" is used.
|
||||||
|
// - Some functions like `GetTeamsWithAccessToAnyRepoUnit` use both.
|
||||||
|
//
|
||||||
|
// 3. After introducing "team unit" and more unclear changes, it becomes difficult to maintain team permissions.
|
||||||
|
// - Org owner need to click the permission for each unit, but can't just set a common "write" permission for all units.
|
||||||
|
//
|
||||||
|
// Ideally, "team.authorize=write" should mean the team has write access to all units including newly (future) added ones.
|
||||||
func getUnitPerms(forms url.Values, teamPermission perm.AccessMode) map[unit_model.Type]perm.AccessMode {
|
func getUnitPerms(forms url.Values, teamPermission perm.AccessMode) map[unit_model.Type]perm.AccessMode {
|
||||||
unitPerms := make(map[unit_model.Type]perm.AccessMode)
|
unitPerms := make(map[unit_model.Type]perm.AccessMode)
|
||||||
for _, ut := range unit_model.AllRepoUnitTypes {
|
for _, ut := range unit_model.AllRepoUnitTypes {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/charset"
|
"code.gitea.io/gitea/modules/charset"
|
||||||
|
"code.gitea.io/gitea/modules/fileicon"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@@ -168,10 +169,13 @@ func Graph(ctx *context.Context) {
|
|||||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||||
|
|
||||||
|
divOnly := ctx.FormBool("div-only")
|
||||||
|
queryParams := ctx.Req.URL.Query()
|
||||||
|
queryParams.Del("div-only")
|
||||||
paginator := context.NewPagination(int(graphCommitsCount), setting.UI.GraphMaxCommitNum, page, 5)
|
paginator := context.NewPagination(int(graphCommitsCount), setting.UI.GraphMaxCommitNum, page, 5)
|
||||||
paginator.AddParamFromRequest(ctx.Req)
|
paginator.AddParamFromQuery(queryParams)
|
||||||
ctx.Data["Page"] = paginator
|
ctx.Data["Page"] = paginator
|
||||||
if ctx.FormBool("div-only") {
|
if divOnly {
|
||||||
ctx.HTML(http.StatusOK, tplGraphDiv)
|
ctx.HTML(http.StatusOK, tplGraphDiv)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -313,7 +317,7 @@ func Diff(ctx *context.Context) {
|
|||||||
maxLines, maxFiles = -1, -1
|
maxLines, maxFiles = -1, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
diff, err := gitdiff.GetDiffForRender(ctx, gitRepo, &gitdiff.DiffOptions{
|
diff, err := gitdiff.GetDiffForRender(ctx, ctx.Repo.RepoLink, gitRepo, &gitdiff.DiffOptions{
|
||||||
AfterCommitID: commitID,
|
AfterCommitID: commitID,
|
||||||
SkipTo: ctx.FormString("skip-to"),
|
SkipTo: ctx.FormString("skip-to"),
|
||||||
MaxLines: maxLines,
|
MaxLines: maxLines,
|
||||||
@@ -369,7 +373,11 @@ func Diff(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, nil)
|
renderedIconPool := fileicon.NewRenderedIconPool()
|
||||||
|
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(renderedIconPool, diffTree, nil)
|
||||||
|
ctx.PageData["FolderIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
|
||||||
|
ctx.PageData["FolderOpenIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolderOpen())
|
||||||
|
ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)
|
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/charset"
|
"code.gitea.io/gitea/modules/charset"
|
||||||
csv_module "code.gitea.io/gitea/modules/csv"
|
csv_module "code.gitea.io/gitea/modules/csv"
|
||||||
|
"code.gitea.io/gitea/modules/fileicon"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@@ -522,7 +523,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo {
|
|||||||
|
|
||||||
// Treat as pull request if both references are branches
|
// Treat as pull request if both references are branches
|
||||||
if ctx.Data["PageIsComparePull"] == nil {
|
if ctx.Data["PageIsComparePull"] == nil {
|
||||||
ctx.Data["PageIsComparePull"] = headIsBranch && baseIsBranch
|
ctx.Data["PageIsComparePull"] = headIsBranch && baseIsBranch && permBase.CanReadIssuesOrPulls(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Data["PageIsComparePull"] == true && !permBase.CanReadIssuesOrPulls(true) {
|
if ctx.Data["PageIsComparePull"] == true && !permBase.CanReadIssuesOrPulls(true) {
|
||||||
@@ -613,7 +614,7 @@ func PrepareCompareDiff(
|
|||||||
|
|
||||||
fileOnly := ctx.FormBool("file-only")
|
fileOnly := ctx.FormBool("file-only")
|
||||||
|
|
||||||
diff, err := gitdiff.GetDiffForRender(ctx, ci.HeadGitRepo,
|
diff, err := gitdiff.GetDiffForRender(ctx, ci.HeadRepo.Link(), ci.HeadGitRepo,
|
||||||
&gitdiff.DiffOptions{
|
&gitdiff.DiffOptions{
|
||||||
BeforeCommitID: beforeCommitID,
|
BeforeCommitID: beforeCommitID,
|
||||||
AfterCommitID: headCommitID,
|
AfterCommitID: headCommitID,
|
||||||
@@ -644,7 +645,11 @@ func PrepareCompareDiff(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, nil)
|
renderedIconPool := fileicon.NewRenderedIconPool()
|
||||||
|
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(renderedIconPool, diffTree, nil)
|
||||||
|
ctx.PageData["FolderIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
|
||||||
|
ctx.PageData["FolderOpenIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolderOpen())
|
||||||
|
ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
|
||||||
}
|
}
|
||||||
|
|
||||||
headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID)
|
headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID)
|
||||||
@@ -730,6 +735,7 @@ func CompareDiff(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.Data["PageIsViewCode"] = true
|
||||||
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
||||||
ctx.Data["DirectComparison"] = ci.DirectComparison
|
ctx.Data["DirectComparison"] = ci.DirectComparison
|
||||||
ctx.Data["OtherCompareSeparator"] = ".."
|
ctx.Data["OtherCompareSeparator"] = ".."
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/emoji"
|
"code.gitea.io/gitea/modules/emoji"
|
||||||
|
"code.gitea.io/gitea/modules/fileicon"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
@@ -642,8 +643,17 @@ func ViewPullCommits(ctx *context.Context) {
|
|||||||
ctx.HTML(http.StatusOK, tplPullCommits)
|
ctx.HTML(http.StatusOK, tplPullCommits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func indexCommit(commits []*git.Commit, commitID string) *git.Commit {
|
||||||
|
for i := range commits {
|
||||||
|
if commits[i].ID.String() == commitID {
|
||||||
|
return commits[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ViewPullFiles render pull request changed files list page
|
// ViewPullFiles render pull request changed files list page
|
||||||
func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommit string, willShowSpecifiedCommitRange, willShowSpecifiedCommit bool) {
|
func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
|
||||||
ctx.Data["PageIsPullList"] = true
|
ctx.Data["PageIsPullList"] = true
|
||||||
ctx.Data["PageIsPullFiles"] = true
|
ctx.Data["PageIsPullFiles"] = true
|
||||||
|
|
||||||
@@ -653,11 +663,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
|||||||
}
|
}
|
||||||
pull := issue.PullRequest
|
pull := issue.PullRequest
|
||||||
|
|
||||||
var (
|
gitRepo := ctx.Repo.GitRepo
|
||||||
startCommitID string
|
|
||||||
endCommitID string
|
|
||||||
gitRepo = ctx.Repo.GitRepo
|
|
||||||
)
|
|
||||||
|
|
||||||
prInfo := preparePullViewPullInfo(ctx, issue)
|
prInfo := preparePullViewPullInfo(ctx, issue)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
@@ -667,77 +673,68 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the given commit sha to show (if any passed)
|
|
||||||
if willShowSpecifiedCommit || willShowSpecifiedCommitRange {
|
|
||||||
foundStartCommit := len(specifiedStartCommit) == 0
|
|
||||||
foundEndCommit := len(specifiedEndCommit) == 0
|
|
||||||
|
|
||||||
if !(foundStartCommit && foundEndCommit) {
|
|
||||||
for _, commit := range prInfo.Commits {
|
|
||||||
if commit.ID.String() == specifiedStartCommit {
|
|
||||||
foundStartCommit = true
|
|
||||||
}
|
|
||||||
if commit.ID.String() == specifiedEndCommit {
|
|
||||||
foundEndCommit = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if foundStartCommit && foundEndCommit {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(foundStartCommit && foundEndCommit) {
|
|
||||||
ctx.NotFound(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
|
headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetRefCommitID", err)
|
ctx.ServerError("GetRefCommitID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["IsShowingOnlySingleCommit"] = willShowSpecifiedCommit
|
isSingleCommit := beforeCommitID == "" && afterCommitID != ""
|
||||||
|
ctx.Data["IsShowingOnlySingleCommit"] = isSingleCommit
|
||||||
|
isShowAllCommits := (beforeCommitID == "" || beforeCommitID == prInfo.MergeBase) && (afterCommitID == "" || afterCommitID == headCommitID)
|
||||||
|
ctx.Data["IsShowingAllCommits"] = isShowAllCommits
|
||||||
|
|
||||||
if willShowSpecifiedCommit || willShowSpecifiedCommitRange {
|
if afterCommitID == "" || afterCommitID == headCommitID {
|
||||||
if len(specifiedEndCommit) > 0 {
|
afterCommitID = headCommitID
|
||||||
endCommitID = specifiedEndCommit
|
|
||||||
} else {
|
|
||||||
endCommitID = headCommitID
|
|
||||||
}
|
}
|
||||||
if len(specifiedStartCommit) > 0 {
|
afterCommit := indexCommit(prInfo.Commits, afterCommitID)
|
||||||
startCommitID = specifiedStartCommit
|
if afterCommit == nil {
|
||||||
} else {
|
ctx.HTTPError(http.StatusBadRequest, "after commit not found in PR commits")
|
||||||
startCommitID = prInfo.MergeBase
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var beforeCommit *git.Commit
|
||||||
|
if !isSingleCommit {
|
||||||
|
if beforeCommitID == "" || beforeCommitID == prInfo.MergeBase {
|
||||||
|
beforeCommitID = prInfo.MergeBase
|
||||||
|
// mergebase commit is not in the list of the pull request commits
|
||||||
|
beforeCommit, err = gitRepo.GetCommit(beforeCommitID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetCommit", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
ctx.Data["IsShowingAllCommits"] = false
|
|
||||||
} else {
|
} else {
|
||||||
endCommitID = headCommitID
|
beforeCommit = indexCommit(prInfo.Commits, beforeCommitID)
|
||||||
startCommitID = prInfo.MergeBase
|
if beforeCommit == nil {
|
||||||
ctx.Data["IsShowingAllCommits"] = true
|
ctx.HTTPError(http.StatusBadRequest, "before commit not found in PR commits")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
beforeCommit, err = afterCommit.Parent(0)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("Parent", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
beforeCommitID = beforeCommit.ID.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||||
ctx.Data["AfterCommitID"] = endCommitID
|
ctx.Data["MergeBase"] = prInfo.MergeBase
|
||||||
ctx.Data["BeforeCommitID"] = startCommitID
|
ctx.Data["AfterCommitID"] = afterCommitID
|
||||||
|
ctx.Data["BeforeCommitID"] = beforeCommitID
|
||||||
fileOnly := ctx.FormBool("file-only")
|
|
||||||
|
|
||||||
maxLines, maxFiles := setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles
|
maxLines, maxFiles := setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles
|
||||||
files := ctx.FormStrings("files")
|
files := ctx.FormStrings("files")
|
||||||
|
fileOnly := ctx.FormBool("file-only")
|
||||||
if fileOnly && (len(files) == 2 || len(files) == 1) {
|
if fileOnly && (len(files) == 2 || len(files) == 1) {
|
||||||
maxLines, maxFiles = -1, -1
|
maxLines, maxFiles = -1, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
diffOptions := &gitdiff.DiffOptions{
|
diffOptions := &gitdiff.DiffOptions{
|
||||||
AfterCommitID: endCommitID,
|
BeforeCommitID: beforeCommitID,
|
||||||
|
AfterCommitID: afterCommitID,
|
||||||
SkipTo: ctx.FormString("skip-to"),
|
SkipTo: ctx.FormString("skip-to"),
|
||||||
MaxLines: maxLines,
|
MaxLines: maxLines,
|
||||||
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
|
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
|
||||||
@@ -745,11 +742,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
|||||||
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)),
|
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !willShowSpecifiedCommit {
|
diff, err := gitdiff.GetDiffForRender(ctx, ctx.Repo.RepoLink, gitRepo, diffOptions, files...)
|
||||||
diffOptions.BeforeCommitID = startCommitID
|
|
||||||
}
|
|
||||||
|
|
||||||
diff, err := gitdiff.GetDiffForRender(ctx, gitRepo, diffOptions, files...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetDiff", err)
|
ctx.ServerError("GetDiff", err)
|
||||||
return
|
return
|
||||||
@@ -760,7 +753,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
|||||||
// as the viewed information is designed to be loaded only on latest PR
|
// as the viewed information is designed to be loaded only on latest PR
|
||||||
// diff and if you're signed in.
|
// diff and if you're signed in.
|
||||||
var reviewState *pull_model.ReviewState
|
var reviewState *pull_model.ReviewState
|
||||||
if ctx.IsSigned && !willShowSpecifiedCommit && !willShowSpecifiedCommitRange {
|
if ctx.IsSigned && isShowAllCommits {
|
||||||
reviewState, err = gitdiff.SyncUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diff, diffOptions)
|
reviewState, err = gitdiff.SyncUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diff, diffOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SyncUserSpecificDiff", err)
|
ctx.ServerError("SyncUserSpecificDiff", err)
|
||||||
@@ -768,7 +761,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, startCommitID, endCommitID)
|
diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, beforeCommitID, afterCommitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetDiffShortStat", err)
|
ctx.ServerError("GetDiffShortStat", err)
|
||||||
return
|
return
|
||||||
@@ -815,7 +808,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
|||||||
|
|
||||||
if !fileOnly {
|
if !fileOnly {
|
||||||
// note: use mergeBase is set to false because we already have the merge base from the pull request info
|
// note: use mergeBase is set to false because we already have the merge base from the pull request info
|
||||||
diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, startCommitID, endCommitID)
|
diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, beforeCommitID, afterCommitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetDiffTree", err)
|
ctx.ServerError("GetDiffTree", err)
|
||||||
return
|
return
|
||||||
@@ -824,23 +817,17 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
|||||||
if reviewState != nil {
|
if reviewState != nil {
|
||||||
filesViewedState = reviewState.UpdatedFiles
|
filesViewedState = reviewState.UpdatedFiles
|
||||||
}
|
}
|
||||||
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, filesViewedState)
|
|
||||||
|
renderedIconPool := fileicon.NewRenderedIconPool()
|
||||||
|
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(renderedIconPool, diffTree, filesViewedState)
|
||||||
|
ctx.PageData["FolderIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
|
||||||
|
ctx.PageData["FolderOpenIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolderOpen())
|
||||||
|
ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["Diff"] = diff
|
ctx.Data["Diff"] = diff
|
||||||
ctx.Data["DiffNotAvailable"] = diffShortStat.NumFiles == 0
|
ctx.Data["DiffNotAvailable"] = diffShortStat.NumFiles == 0
|
||||||
|
|
||||||
baseCommit, err := ctx.Repo.GitRepo.GetCommit(startCommitID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetCommit", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
commit, err := gitRepo.GetCommit(endCommitID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetCommit", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.IsSigned && ctx.Doer != nil {
|
if ctx.IsSigned && ctx.Doer != nil {
|
||||||
if ctx.Data["CanMarkConversation"], err = issues_model.CanMarkConversation(ctx, issue, ctx.Doer); err != nil {
|
if ctx.Data["CanMarkConversation"], err = issues_model.CanMarkConversation(ctx, issue, ctx.Doer); err != nil {
|
||||||
ctx.ServerError("CanMarkConversation", err)
|
ctx.ServerError("CanMarkConversation", err)
|
||||||
@@ -848,7 +835,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setCompareContext(ctx, baseCommit, commit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
|
setCompareContext(ctx, beforeCommit, afterCommit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
|
||||||
|
|
||||||
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
|
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -895,7 +882,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
|||||||
ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool {
|
ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool {
|
||||||
return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee)
|
return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee)
|
||||||
}
|
}
|
||||||
if !willShowSpecifiedCommit && !willShowSpecifiedCommitRange && pull.Flow == issues_model.PullRequestFlowGithub {
|
if isShowAllCommits && pull.Flow == issues_model.PullRequestFlowGithub {
|
||||||
if err := pull.LoadHeadRepo(ctx); err != nil {
|
if err := pull.LoadHeadRepo(ctx); err != nil {
|
||||||
ctx.ServerError("LoadHeadRepo", err)
|
ctx.ServerError("LoadHeadRepo", err)
|
||||||
return
|
return
|
||||||
@@ -924,19 +911,17 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ViewPullFilesForSingleCommit(ctx *context.Context) {
|
func ViewPullFilesForSingleCommit(ctx *context.Context) {
|
||||||
viewPullFiles(ctx, "", ctx.PathParam("sha"), true, true)
|
// it doesn't support showing files from mergebase to the special commit
|
||||||
|
// otherwise it will be ambiguous
|
||||||
|
viewPullFiles(ctx, "", ctx.PathParam("sha"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ViewPullFilesForRange(ctx *context.Context) {
|
func ViewPullFilesForRange(ctx *context.Context) {
|
||||||
viewPullFiles(ctx, ctx.PathParam("shaFrom"), ctx.PathParam("shaTo"), true, false)
|
viewPullFiles(ctx, ctx.PathParam("shaFrom"), ctx.PathParam("shaTo"))
|
||||||
}
|
|
||||||
|
|
||||||
func ViewPullFilesStartingFromCommit(ctx *context.Context) {
|
|
||||||
viewPullFiles(ctx, "", ctx.PathParam("sha"), true, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ViewPullFilesForAllCommitsOfPr(ctx *context.Context) {
|
func ViewPullFilesForAllCommitsOfPr(ctx *context.Context) {
|
||||||
viewPullFiles(ctx, "", "", false, false)
|
viewPullFiles(ctx, "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePullRequest merge PR's baseBranch into headBranch
|
// UpdatePullRequest merge PR's baseBranch into headBranch
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
@@ -89,7 +90,7 @@ func SettingsProtectedBranch(c *context.Context) {
|
|||||||
c.Data["recent_status_checks"] = contexts
|
c.Data["recent_status_checks"] = contexts
|
||||||
|
|
||||||
if c.Repo.Owner.IsOrganization() {
|
if c.Repo.Owner.IsOrganization() {
|
||||||
teams, err := organization.OrgFromUser(c.Repo.Owner).TeamsWithAccessToRepo(c, c.Repo.Repository.ID, perm.AccessModeRead)
|
teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(c, c.Repo.Owner.ID, c.Repo.Repository.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
|
c.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
@@ -156,7 +157,7 @@ func setTagsContext(ctx *context.Context) error {
|
|||||||
ctx.Data["Users"] = users
|
ctx.Data["Users"] = users
|
||||||
|
|
||||||
if ctx.Repo.Owner.IsOrganization() {
|
if ctx.Repo.Owner.IsOrganization() {
|
||||||
teams, err := organization.OrgFromUser(ctx.Repo.Owner).TeamsWithAccessToRepo(ctx, ctx.Repo.Repository.ID, perm.AccessModeRead)
|
teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, ctx.Repo.Owner.ID, ctx.Repo.Repository.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
|
ctx.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ type WebDiffFileItem struct {
|
|||||||
EntryMode string
|
EntryMode string
|
||||||
IsViewed bool
|
IsViewed bool
|
||||||
Children []*WebDiffFileItem
|
Children []*WebDiffFileItem
|
||||||
// TODO: add icon support in the future
|
FileIcon template.HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebDiffFileTree is used by frontend, check the field names in frontend before changing
|
// WebDiffFileTree is used by frontend, check the field names in frontend before changing
|
||||||
@@ -77,7 +78,7 @@ type WebDiffFileTree struct {
|
|||||||
|
|
||||||
// transformDiffTreeForWeb transforms a gitdiff.DiffTree into a WebDiffFileTree for Web UI rendering
|
// transformDiffTreeForWeb transforms a gitdiff.DiffTree into a WebDiffFileTree for Web UI rendering
|
||||||
// it also takes a map of file names to their viewed state, which is used to mark files as viewed
|
// it also takes a map of file names to their viewed state, which is used to mark files as viewed
|
||||||
func transformDiffTreeForWeb(diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) (dft WebDiffFileTree) {
|
func transformDiffTreeForWeb(renderedIconPool *fileicon.RenderedIconPool, diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) (dft WebDiffFileTree) {
|
||||||
dirNodes := map[string]*WebDiffFileItem{"": &dft.TreeRoot}
|
dirNodes := map[string]*WebDiffFileItem{"": &dft.TreeRoot}
|
||||||
addItem := func(item *WebDiffFileItem) {
|
addItem := func(item *WebDiffFileItem) {
|
||||||
var parentPath string
|
var parentPath string
|
||||||
@@ -110,6 +111,7 @@ func transformDiffTreeForWeb(diffTree *gitdiff.DiffTree, filesViewedState map[st
|
|||||||
item := &WebDiffFileItem{FullName: file.HeadPath, DiffStatus: file.Status}
|
item := &WebDiffFileItem{FullName: file.HeadPath, DiffStatus: file.Status}
|
||||||
item.IsViewed = filesViewedState[item.FullName] == pull_model.Viewed
|
item.IsViewed = filesViewedState[item.FullName] == pull_model.Viewed
|
||||||
item.NameHash = git.HashFilePathForWebUI(item.FullName)
|
item.NameHash = git.HashFilePathForWebUI(item.FullName)
|
||||||
|
item.FileIcon = fileicon.RenderEntryIconHTML(renderedIconPool, &fileicon.EntryInfo{FullName: file.HeadPath, EntryMode: file.HeadMode})
|
||||||
|
|
||||||
switch file.HeadMode {
|
switch file.HeadMode {
|
||||||
case git.EntryModeTree:
|
case git.EntryModeTree:
|
||||||
@@ -141,7 +143,7 @@ func transformDiffTreeForWeb(diffTree *gitdiff.DiffTree, filesViewedState map[st
|
|||||||
|
|
||||||
func TreeViewNodes(ctx *context.Context) {
|
func TreeViewNodes(ctx *context.Context) {
|
||||||
renderedIconPool := fileicon.NewRenderedIconPool()
|
renderedIconPool := fileicon.NewRenderedIconPool()
|
||||||
results, err := files_service.GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, ctx.Repo.TreePath, ctx.FormString("sub_path"))
|
results, err := files_service.GetTreeViewNodes(ctx, ctx.Repo.RepoLink, renderedIconPool, ctx.Repo.Commit, ctx.Repo.TreePath, ctx.FormString("sub_path"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetTreeViewNodes", err)
|
ctx.ServerError("GetTreeViewNodes", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html/template"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
pull_model "code.gitea.io/gitea/models/pull"
|
pull_model "code.gitea.io/gitea/models/pull"
|
||||||
|
"code.gitea.io/gitea/modules/fileicon"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/services/gitdiff"
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
|
|
||||||
@@ -14,7 +16,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestTransformDiffTreeForWeb(t *testing.T) {
|
func TestTransformDiffTreeForWeb(t *testing.T) {
|
||||||
ret := transformDiffTreeForWeb(&gitdiff.DiffTree{Files: []*gitdiff.DiffTreeRecord{
|
renderedIconPool := fileicon.NewRenderedIconPool()
|
||||||
|
ret := transformDiffTreeForWeb(renderedIconPool, &gitdiff.DiffTree{Files: []*gitdiff.DiffTreeRecord{
|
||||||
{
|
{
|
||||||
Status: "changed",
|
Status: "changed",
|
||||||
HeadPath: "dir-a/dir-a-x/file-deep",
|
HeadPath: "dir-a/dir-a-x/file-deep",
|
||||||
@@ -29,6 +32,9 @@ func TestTransformDiffTreeForWeb(t *testing.T) {
|
|||||||
"dir-a/dir-a-x/file-deep": pull_model.Viewed,
|
"dir-a/dir-a-x/file-deep": pull_model.Viewed,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mockIconForFile := func(id string) template.HTML {
|
||||||
|
return template.HTML(`<svg class="svg git-entry-icon octicon-file" width="16" height="16" aria-hidden="true"><use xlink:href="#` + id + `"></use></svg>`)
|
||||||
|
}
|
||||||
assert.Equal(t, WebDiffFileTree{
|
assert.Equal(t, WebDiffFileTree{
|
||||||
TreeRoot: WebDiffFileItem{
|
TreeRoot: WebDiffFileItem{
|
||||||
Children: []*WebDiffFileItem{
|
Children: []*WebDiffFileItem{
|
||||||
@@ -44,6 +50,7 @@ func TestTransformDiffTreeForWeb(t *testing.T) {
|
|||||||
NameHash: "4acf7eef1c943a09e9f754e93ff190db8583236b",
|
NameHash: "4acf7eef1c943a09e9f754e93ff190db8583236b",
|
||||||
DiffStatus: "changed",
|
DiffStatus: "changed",
|
||||||
IsViewed: true,
|
IsViewed: true,
|
||||||
|
FileIcon: mockIconForFile(`svg-mfi-file`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -53,6 +60,7 @@ func TestTransformDiffTreeForWeb(t *testing.T) {
|
|||||||
FullName: "file1",
|
FullName: "file1",
|
||||||
NameHash: "60b27f004e454aca81b0480209cce5081ec52390",
|
NameHash: "60b27f004e454aca81b0480209cce5081ec52390",
|
||||||
DiffStatus: "added",
|
DiffStatus: "added",
|
||||||
|
FileIcon: mockIconForFile(`svg-mfi-file`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -257,8 +257,9 @@ func prepareDirectoryFileIcons(ctx *context.Context, files []git.CommitInfo) {
|
|||||||
renderedIconPool := fileicon.NewRenderedIconPool()
|
renderedIconPool := fileicon.NewRenderedIconPool()
|
||||||
fileIcons := map[string]template.HTML{}
|
fileIcons := map[string]template.HTML{}
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
fileIcons[f.Entry.Name()] = fileicon.RenderEntryIcon(renderedIconPool, f.Entry)
|
fileIcons[f.Entry.Name()] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFromGitTreeEntry(f.Entry))
|
||||||
}
|
}
|
||||||
|
fileIcons[".."] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
|
||||||
ctx.Data["FileIcons"] = fileIcons
|
ctx.Data["FileIcons"] = fileIcons
|
||||||
ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
|
ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
|
||||||
}
|
}
|
||||||
@@ -298,7 +299,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
files, latestCommit, err := allEntries.GetCommitsInfo(commitInfoCtx, ctx.Repo.Commit, ctx.Repo.TreePath)
|
files, latestCommit, err := allEntries.GetCommitsInfo(commitInfoCtx, ctx.Repo.RepoLink, ctx.Repo.Commit, ctx.Repo.TreePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetCommitsInfo", err)
|
ctx.ServerError("GetCommitsInfo", err)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import (
|
|||||||
unit_model "code.gitea.io/gitea/models/unit"
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
giturl "code.gitea.io/gitea/modules/git/url"
|
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
|
"code.gitea.io/gitea/modules/htmlutil"
|
||||||
"code.gitea.io/gitea/modules/httplib"
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
@@ -309,34 +309,41 @@ func handleRepoEmptyOrBroken(ctx *context.Context) {
|
|||||||
ctx.Redirect(link)
|
ctx.Redirect(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRepoViewSubmodule(ctx *context.Context, submodule *git.SubModule) {
|
func isViewHomeOnlyContent(ctx *context.Context) bool {
|
||||||
submoduleRepoURL, err := giturl.ParseRepositoryURL(ctx, submodule.URL)
|
return ctx.FormBool("only_content")
|
||||||
if err != nil {
|
}
|
||||||
HandleGitError(ctx, "prepareToRenderDirOrFile: ParseRepositoryURL", err)
|
|
||||||
|
func handleRepoViewSubmodule(ctx *context.Context, commitSubmoduleFile *git.CommitSubmoduleFile) {
|
||||||
|
submoduleWebLink := commitSubmoduleFile.SubmoduleWebLinkTree(ctx)
|
||||||
|
if submoduleWebLink == nil {
|
||||||
|
ctx.Data["NotFoundPrompt"] = ctx.Repo.TreePath
|
||||||
|
ctx.NotFound(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
submoduleURL := giturl.MakeRepositoryWebLink(submoduleRepoURL)
|
|
||||||
if httplib.IsCurrentGiteaSiteURL(ctx, submoduleURL) {
|
redirectLink := submoduleWebLink.CommitWebLink
|
||||||
ctx.RedirectToCurrentSite(submoduleURL)
|
if isViewHomeOnlyContent(ctx) {
|
||||||
} else {
|
ctx.Resp.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
_, _ = ctx.Resp.Write([]byte(htmlutil.HTMLFormat(`<a href="%s">%s</a>`, redirectLink, redirectLink)))
|
||||||
|
} else if !httplib.IsCurrentGiteaSiteURL(ctx, redirectLink) {
|
||||||
// don't auto-redirect to external URL, to avoid open redirect or phishing
|
// don't auto-redirect to external URL, to avoid open redirect or phishing
|
||||||
ctx.Data["NotFoundPrompt"] = submoduleURL
|
ctx.Data["NotFoundPrompt"] = redirectLink
|
||||||
ctx.NotFound(nil)
|
ctx.NotFound(nil)
|
||||||
|
} else {
|
||||||
|
ctx.Redirect(submoduleWebLink.CommitWebLink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) {
|
func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) {
|
||||||
return func(ctx *context.Context) {
|
return func(ctx *context.Context) {
|
||||||
if entry.IsSubModule() {
|
if entry.IsSubModule() {
|
||||||
submodule, err := ctx.Repo.Commit.GetSubModule(entry.Name())
|
commitSubmoduleFile, err := git.GetCommitInfoSubmoduleFile(ctx.Repo.RepoLink, ctx.Repo.TreePath, ctx.Repo.Commit, entry.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
HandleGitError(ctx, "prepareToRenderDirOrFile: GetSubModule", err)
|
HandleGitError(ctx, "prepareToRenderDirOrFile: GetCommitInfoSubmoduleFile", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handleRepoViewSubmodule(ctx, submodule)
|
handleRepoViewSubmodule(ctx, commitSubmoduleFile)
|
||||||
return
|
} else if entry.IsDir() {
|
||||||
}
|
|
||||||
if entry.IsDir() {
|
|
||||||
prepareToRenderDirectory(ctx)
|
prepareToRenderDirectory(ctx)
|
||||||
} else {
|
} else {
|
||||||
prepareToRenderFile(ctx, entry)
|
prepareToRenderFile(ctx, entry)
|
||||||
@@ -472,7 +479,7 @@ func Home(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.FormBool("only_content") {
|
if isViewHomeOnlyContent(ctx) {
|
||||||
ctx.HTML(http.StatusOK, tplRepoViewContent)
|
ctx.HTML(http.StatusOK, tplRepoViewContent)
|
||||||
} else if len(treeNames) != 0 {
|
} else if len(treeNames) != 0 {
|
||||||
ctx.HTML(http.StatusOK, tplRepoView)
|
ctx.HTML(http.StatusOK, tplRepoView)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
git_module "code.gitea.io/gitea/modules/git"
|
git_module "code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/services/contexttest"
|
"code.gitea.io/gitea/services/contexttest"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -19,14 +18,20 @@ func TestViewHomeSubmoduleRedirect(t *testing.T) {
|
|||||||
unittest.PrepareTestEnv(t)
|
unittest.PrepareTestEnv(t)
|
||||||
|
|
||||||
ctx, _ := contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule")
|
ctx, _ := contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule")
|
||||||
submodule := &git_module.SubModule{Path: "test-submodule", URL: setting.AppURL + "user2/repo-other.git"}
|
submodule := git_module.NewCommitSubmoduleFile("/user2/repo1", "test-submodule", "../repo-other", "any-ref-id")
|
||||||
handleRepoViewSubmodule(ctx, submodule)
|
handleRepoViewSubmodule(ctx, submodule)
|
||||||
assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus())
|
assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus())
|
||||||
assert.Equal(t, "/user2/repo-other", ctx.Resp.Header().Get("Location"))
|
assert.Equal(t, "/user2/repo-other/tree/any-ref-id", ctx.Resp.Header().Get("Location"))
|
||||||
|
|
||||||
ctx, _ = contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule")
|
ctx, _ = contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule")
|
||||||
submodule = &git_module.SubModule{Path: "test-submodule", URL: "https://other/user2/repo-other.git"}
|
submodule = git_module.NewCommitSubmoduleFile("/user2/repo1", "test-submodule", "https://other/user2/repo-other.git", "any-ref-id")
|
||||||
handleRepoViewSubmodule(ctx, submodule)
|
handleRepoViewSubmodule(ctx, submodule)
|
||||||
// do not auto-redirect for external URLs, to avoid open redirect or phishing
|
// do not auto-redirect for external URLs, to avoid open redirect or phishing
|
||||||
assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus())
|
assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus())
|
||||||
|
|
||||||
|
ctx, respWriter := contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule?only_content=true")
|
||||||
|
submodule = git_module.NewCommitSubmoduleFile("/user2/repo1", "test-submodule", "../repo-other", "any-ref-id")
|
||||||
|
handleRepoViewSubmodule(ctx, submodule)
|
||||||
|
assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus())
|
||||||
|
assert.Equal(t, `<a href="/user2/repo-other/tree/any-ref-id">/user2/repo-other/tree/any-ref-id</a>`, respWriter.Body.String())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package repo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
gocontext "context"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -666,7 +665,7 @@ func WikiPages(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
allEntries.CustomSort(base.NaturalSortLess)
|
allEntries.CustomSort(base.NaturalSortLess)
|
||||||
|
|
||||||
entries, _, err := allEntries.GetCommitsInfo(gocontext.Context(ctx), commit, treePath)
|
entries, _, err := allEntries.GetCommitsInfo(ctx, ctx.Repo.RepoLink, commit, treePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetCommitsInfo", err)
|
ctx.ServerError("GetCommitsInfo", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1217,10 +1217,11 @@ func registerWebRoutes(m *web.Router) {
|
|||||||
// end "/{username}/{reponame}": view milestone, label, issue, pull, etc
|
// end "/{username}/{reponame}": view milestone, label, issue, pull, etc
|
||||||
|
|
||||||
m.Group("/{username}/{reponame}/{type:issues}", func() {
|
m.Group("/{username}/{reponame}/{type:issues}", func() {
|
||||||
|
// these handlers also check unit permissions internally
|
||||||
m.Get("", repo.Issues)
|
m.Get("", repo.Issues)
|
||||||
m.Get("/{index}", repo.ViewIssue)
|
m.Get("/{index}", repo.ViewIssue) // also do pull-request redirection (".../issues/{PR-number}" -> ".../pulls/{PR-number}")
|
||||||
}, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeIssues, unit.TypeExternalTracker))
|
}, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeIssues, unit.TypePullRequests, unit.TypeExternalTracker))
|
||||||
// end "/{username}/{reponame}": issue/pull list, issue/pull view, external tracker
|
// end "/{username}/{reponame}": issue list, issue view (pull-request redirection), external tracker
|
||||||
|
|
||||||
m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc
|
m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc
|
||||||
m.Group("/issues", func() {
|
m.Group("/issues", func() {
|
||||||
@@ -1509,7 +1510,7 @@ func registerWebRoutes(m *web.Router) {
|
|||||||
m.Group("/commits", func() {
|
m.Group("/commits", func() {
|
||||||
m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits)
|
m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits)
|
||||||
m.Get("/list", repo.GetPullCommits)
|
m.Get("/list", repo.GetPullCommits)
|
||||||
m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
|
m.Get("/{sha:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
|
||||||
})
|
})
|
||||||
m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest)
|
m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest)
|
||||||
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
|
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
|
||||||
@@ -1518,8 +1519,7 @@ func registerWebRoutes(m *web.Router) {
|
|||||||
m.Post("/cleanup", context.RepoMustNotBeArchived(), repo.CleanUpPullRequest)
|
m.Post("/cleanup", context.RepoMustNotBeArchived(), repo.CleanUpPullRequest)
|
||||||
m.Group("/files", func() {
|
m.Group("/files", func() {
|
||||||
m.Get("", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr)
|
m.Get("", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr)
|
||||||
m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit)
|
m.Get("/{shaFrom:[a-f0-9]{7,64}}..{shaTo:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange)
|
||||||
m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange)
|
|
||||||
m.Group("/reviews", func() {
|
m.Group("/reviews", func() {
|
||||||
m.Get("/new_comment", repo.RenderNewCodeCommentForm)
|
m.Get("/new_comment", repo.RenderNewCodeCommentForm)
|
||||||
m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment)
|
m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment)
|
||||||
@@ -1593,8 +1593,8 @@ func registerWebRoutes(m *web.Router) {
|
|||||||
m.Get("/cherry-pick/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, context.RepoRefByDefaultBranch(), repo.CherryPick)
|
m.Get("/cherry-pick/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, context.RepoRefByDefaultBranch(), repo.CherryPick)
|
||||||
}, repo.MustBeNotEmpty)
|
}, repo.MustBeNotEmpty)
|
||||||
|
|
||||||
m.Get("/rss/branch/*", context.RepoRefByType(git.RefTypeBranch), feedEnabled, feed.RenderBranchFeed)
|
m.Get("/rss/branch/*", context.RepoRefByType(git.RefTypeBranch), feedEnabled, feed.RenderBranchFeedRSS)
|
||||||
m.Get("/atom/branch/*", context.RepoRefByType(git.RefTypeBranch), feedEnabled, feed.RenderBranchFeed)
|
m.Get("/atom/branch/*", context.RepoRefByType(git.RefTypeBranch), feedEnabled, feed.RenderBranchFeedAtom)
|
||||||
|
|
||||||
m.Group("/src", func() {
|
m.Group("/src", func() {
|
||||||
m.Get("", func(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink) }) // there is no "{owner}/{repo}/src" page, so redirect to "{owner}/{repo}" to avoid 404
|
m.Get("", func(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink) }) // there is no "{owner}/{repo}/src" page, so redirect to "{owner}/{repo}" to avoid 404
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func CreateAuthorizationToken(taskID, runID, jobID int64) (string, error) {
|
|||||||
|
|
||||||
claims := actionsClaims{
|
claims := actionsClaims{
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
ExpiresAt: jwt.NewNumericDate(now.Add(24 * time.Hour)),
|
ExpiresAt: jwt.NewNumericDate(now.Add(1*time.Hour + setting.Actions.EndlessTaskTimeout)),
|
||||||
NotBefore: jwt.NewNumericDate(now),
|
NotBefore: jwt.NewNumericDate(now),
|
||||||
},
|
},
|
||||||
Scp: fmt.Sprintf("Actions.Results:%d:%d", runID, jobID),
|
Scp: fmt.Sprintf("Actions.Results:%d:%d", runID, jobID),
|
||||||
|
|||||||
@@ -260,11 +260,6 @@ func (n *actionsNotifier) CreateIssueComment(ctx context.Context, doer *user_mod
|
|||||||
func (n *actionsNotifier) UpdateComment(ctx context.Context, doer *user_model.User, c *issues_model.Comment, oldContent string) {
|
func (n *actionsNotifier) UpdateComment(ctx context.Context, doer *user_model.User, c *issues_model.Comment, oldContent string) {
|
||||||
ctx = withMethod(ctx, "UpdateComment")
|
ctx = withMethod(ctx, "UpdateComment")
|
||||||
|
|
||||||
if err := c.LoadIssue(ctx); err != nil {
|
|
||||||
log.Error("LoadIssue: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Issue.IsPull {
|
if c.Issue.IsPull {
|
||||||
notifyIssueCommentChange(ctx, doer, c, oldContent, webhook_module.HookEventPullRequestComment, api.HookIssueCommentEdited)
|
notifyIssueCommentChange(ctx, doer, c, oldContent, webhook_module.HookEventPullRequestComment, api.HookIssueCommentEdited)
|
||||||
return
|
return
|
||||||
@@ -275,11 +270,6 @@ func (n *actionsNotifier) UpdateComment(ctx context.Context, doer *user_model.Us
|
|||||||
func (n *actionsNotifier) DeleteComment(ctx context.Context, doer *user_model.User, comment *issues_model.Comment) {
|
func (n *actionsNotifier) DeleteComment(ctx context.Context, doer *user_model.User, comment *issues_model.Comment) {
|
||||||
ctx = withMethod(ctx, "DeleteComment")
|
ctx = withMethod(ctx, "DeleteComment")
|
||||||
|
|
||||||
if err := comment.LoadIssue(ctx); err != nil {
|
|
||||||
log.Error("LoadIssue: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if comment.Issue.IsPull {
|
if comment.Issue.IsPull {
|
||||||
notifyIssueCommentChange(ctx, doer, comment, "", webhook_module.HookEventPullRequestComment, api.HookIssueCommentDeleted)
|
notifyIssueCommentChange(ctx, doer, comment, "", webhook_module.HookEventPullRequestComment, api.HookIssueCommentDeleted)
|
||||||
return
|
return
|
||||||
@@ -288,6 +278,7 @@ func (n *actionsNotifier) DeleteComment(ctx context.Context, doer *user_model.Us
|
|||||||
}
|
}
|
||||||
|
|
||||||
func notifyIssueCommentChange(ctx context.Context, doer *user_model.User, comment *issues_model.Comment, oldContent string, event webhook_module.HookEventType, action api.HookIssueCommentAction) {
|
func notifyIssueCommentChange(ctx context.Context, doer *user_model.User, comment *issues_model.Comment, oldContent string, event webhook_module.HookEventType, action api.HookIssueCommentAction) {
|
||||||
|
comment.Issue = nil // force issue to be loaded
|
||||||
if err := comment.LoadIssue(ctx); err != nil {
|
if err := comment.LoadIssue(ctx); err != nil {
|
||||||
log.Error("LoadIssue: %v", err)
|
log.Error("LoadIssue: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ package agit
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@@ -17,17 +19,30 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
notify_service "code.gitea.io/gitea/services/notify"
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func parseAgitPushOptionValue(s string) string {
|
||||||
|
if base64Value, ok := strings.CutPrefix(s, "{base64}"); ok {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(base64Value)
|
||||||
|
return util.Iif(err == nil, string(decoded), s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// ProcReceive handle proc receive work
|
// ProcReceive handle proc receive work
|
||||||
func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, opts *private.HookOptions) ([]private.HookProcReceiveRefResult, error) {
|
func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, opts *private.HookOptions) ([]private.HookProcReceiveRefResult, error) {
|
||||||
results := make([]private.HookProcReceiveRefResult, 0, len(opts.OldCommitIDs))
|
results := make([]private.HookProcReceiveRefResult, 0, len(opts.OldCommitIDs))
|
||||||
forcePush := opts.GitPushOptions.Bool(private.GitPushOptionForcePush)
|
forcePush := opts.GitPushOptions.Bool(private.GitPushOptionForcePush)
|
||||||
topicBranch := opts.GitPushOptions["topic"]
|
topicBranch := opts.GitPushOptions["topic"]
|
||||||
title := strings.TrimSpace(opts.GitPushOptions["title"])
|
|
||||||
description := strings.TrimSpace(opts.GitPushOptions["description"])
|
// some options are base64-encoded with "{base64}" prefix if they contain new lines
|
||||||
|
// other agit push options like "issue", "reviewer" and "cc" are not supported
|
||||||
|
title := parseAgitPushOptionValue(opts.GitPushOptions["title"])
|
||||||
|
description := parseAgitPushOptionValue(opts.GitPushOptions["description"])
|
||||||
|
|
||||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
|
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
|
||||||
userName := strings.ToLower(opts.UserName)
|
userName := strings.ToLower(opts.UserName)
|
||||||
|
|
||||||
@@ -199,17 +214,43 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store old commit ID for review staleness checking
|
||||||
|
oldHeadCommitID := pr.HeadCommitID
|
||||||
|
|
||||||
pr.HeadCommitID = opts.NewCommitIDs[i]
|
pr.HeadCommitID = opts.NewCommitIDs[i]
|
||||||
if err = pull_service.UpdateRef(ctx, pr); err != nil {
|
if err = pull_service.UpdateRef(ctx, pr); err != nil {
|
||||||
return nil, fmt.Errorf("failed to update pull ref. Error: %w", err)
|
return nil, fmt.Errorf("failed to update pull ref. Error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark existing reviews as stale when PR content changes (same as regular GitHub flow)
|
||||||
|
if oldHeadCommitID != opts.NewCommitIDs[i] {
|
||||||
|
if err := issues_model.MarkReviewsAsStale(ctx, pr.IssueID); err != nil {
|
||||||
|
log.Error("MarkReviewsAsStale: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dismiss all approval reviews if protected branch rule item enabled
|
||||||
|
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetFirstMatchProtectedBranchRule: %v", err)
|
||||||
|
}
|
||||||
|
if pb != nil && pb.DismissStaleApprovals {
|
||||||
|
if err := pull_service.DismissApprovalReviews(ctx, pusher, pr); err != nil {
|
||||||
|
log.Error("DismissApprovalReviews: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark reviews for the new commit as not stale
|
||||||
|
if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, opts.NewCommitIDs[i]); err != nil {
|
||||||
|
log.Error("MarkReviewsAsNotStale: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pull_service.StartPullRequestCheckImmediately(ctx, pr)
|
pull_service.StartPullRequestCheckImmediately(ctx, pr)
|
||||||
err = pr.LoadIssue(ctx)
|
err = pr.LoadIssue(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load pull issue. Error: %w", err)
|
return nil, fmt.Errorf("failed to load pull issue. Error: %w", err)
|
||||||
}
|
}
|
||||||
comment, err := pull_service.CreatePushPullComment(ctx, pusher, pr, oldCommitID, opts.NewCommitIDs[i])
|
comment, err := pull_service.CreatePushPullComment(ctx, pusher, pr, oldCommitID, opts.NewCommitIDs[i], forcePush.Value())
|
||||||
if err == nil && comment != nil {
|
if err == nil && comment != nil {
|
||||||
notify_service.PullRequestPushCommits(ctx, pusher, pr, comment)
|
notify_service.PullRequestPushCommits(ctx, pusher, pr, comment)
|
||||||
}
|
}
|
||||||
|
|||||||
16
services/agit/agit_test.go
Normal file
16
services/agit/agit_test.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package agit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseAgitPushOptionValue(t *testing.T) {
|
||||||
|
assert.Equal(t, "a", parseAgitPushOptionValue("a"))
|
||||||
|
assert.Equal(t, "a", parseAgitPushOptionValue("{base64}YQ=="))
|
||||||
|
assert.Equal(t, "{base64}invalid value", parseAgitPushOptionValue("{base64}invalid value"))
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user