mirror of
https://github.com/go-gitea/gitea.git
synced 2025-10-03 00:02:14 -04:00
Compare commits
16 Commits
b6f88a77d5
...
a48eb669ec
Author | SHA1 | Date | |
---|---|---|---|
|
a48eb669ec | ||
|
cf290d4fdd | ||
|
b907b9fb1a | ||
|
c5d74e5869 | ||
|
c5332fdc55 | ||
|
0f668145e9 | ||
|
fbe80e6df2 | ||
|
151ef80e28 | ||
|
8106d95577 | ||
|
1f32170060 | ||
|
7bf2972379 | ||
|
8ad2a538da | ||
|
53dfbbb2ee | ||
|
d83676c97a | ||
|
f09bea7af1 | ||
|
0b706b0825 |
6
.github/workflows/pull-db-tests.yml
vendored
6
.github/workflows/pull-db-tests.yml
vendored
@ -72,13 +72,13 @@ jobs:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- run: make deps-backend
|
||||
- run: make backend
|
||||
- run: GOEXPERIMENT='' make backend
|
||||
env:
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
- name: run migration tests
|
||||
run: make test-sqlite-migration
|
||||
- name: run tests
|
||||
run: make test-sqlite
|
||||
run: GOEXPERIMENT='' make test-sqlite
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
@ -142,7 +142,7 @@ jobs:
|
||||
RACE_ENABLED: true
|
||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
||||
- name: unit-tests-gogit
|
||||
run: make unit-test-coverage test-check
|
||||
run: GOEXPERIMENT='' make unit-test-coverage test-check
|
||||
env:
|
||||
TAGS: bindata gogit
|
||||
RACE_ENABLED: true
|
||||
|
6
Makefile
6
Makefile
@ -18,6 +18,10 @@ DIST := dist
|
||||
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
||||
IMPORT := code.gitea.io/gitea
|
||||
|
||||
# By default use go's 1.25 experimental json v2 library when building
|
||||
# TODO: remove when no longer experimental
|
||||
export GOEXPERIMENT ?= jsonv2
|
||||
|
||||
GO ?= go
|
||||
SHASUM ?= shasum -a 256
|
||||
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
|
||||
@ -766,7 +770,7 @@ generate-go: $(TAGS_PREREQ)
|
||||
|
||||
.PHONY: security-check
|
||||
security-check:
|
||||
go run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||
GOEXPERIMENT= go run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||
|
||||
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
|
||||
ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),)
|
||||
|
14
go.mod
14
go.mod
@ -16,7 +16,7 @@ require (
|
||||
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
|
||||
gitea.com/go-chi/cache v0.2.1
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
|
||||
gitea.com/go-chi/session v0.0.0-20250926004215-636cadd82e15
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||
github.com/42wim/httpsig v1.2.3
|
||||
@ -61,6 +61,7 @@ require (
|
||||
github.com/go-redsync/redsync/v4 v4.13.0
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/go-webauthn/webauthn v0.13.4
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
@ -75,7 +76,6 @@ require (
|
||||
github.com/huandu/xstrings v1.5.0
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
|
||||
github.com/jhillyerd/enmime v1.3.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
@ -109,7 +109,7 @@ require (
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/urfave/cli-docs/v3 v3.0.0-alpha6
|
||||
github.com/urfave/cli/v3 v3.4.1
|
||||
github.com/wneessen/go-mail v0.6.2
|
||||
github.com/wneessen/go-mail v0.7.1
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/yohcop/openid-go v1.0.1
|
||||
github.com/yuin/goldmark v1.7.13
|
||||
@ -120,9 +120,9 @@ require (
|
||||
golang.org/x/image v0.30.0
|
||||
golang.org/x/net v0.43.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/sys v0.35.0
|
||||
golang.org/x/text v0.28.0
|
||||
golang.org/x/text v0.29.0
|
||||
google.golang.org/grpc v1.75.0
|
||||
google.golang.org/protobuf v1.36.8
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
@ -200,7 +200,6 @@ require (
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-webauthn/x v0.1.24 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
@ -220,6 +219,7 @@ require (
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/libdns/libdns v1.1.1 // indirect
|
||||
@ -277,7 +277,7 @@ require (
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
|
25
go.sum
25
go.sum
@ -43,8 +43,8 @@ gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
|
||||
gitea.com/go-chi/cache v0.2.1/go.mod h1:Qic0HZ8hOHW62ETGbonpwz8WYypj9NieU9659wFUJ8Q=
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk=
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96 h1:IFDiMBObsP6CZIRaDLd54SR6zPYAffPXiXck5Xslu0Q=
|
||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96/go.mod h1:0iEpFKnwO5dG0aF98O4eq6FMsAiXkNBaDIlUOlq4BtM=
|
||||
gitea.com/go-chi/session v0.0.0-20250926004215-636cadd82e15 h1:qFYmz05u/s9664o7+XEgrlHXSPQ4uHO8/ccZGUb1uxA=
|
||||
gitea.com/go-chi/session v0.0.0-20250926004215-636cadd82e15/go.mod h1:0iEpFKnwO5dG0aF98O4eq6FMsAiXkNBaDIlUOlq4BtM=
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
|
||||
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY=
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
|
||||
@ -765,8 +765,8 @@ github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZ
|
||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||
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/wneessen/go-mail v0.6.2 h1:c6V7c8D2mz868z9WJ+8zDKtUyLfZ1++uAZmo2GRFji8=
|
||||
github.com/wneessen/go-mail v0.6.2/go.mod h1:L/PYjPK3/2ZlNb2/FjEBIn9n1rUWjW+Toy531oVmeb4=
|
||||
github.com/wneessen/go-mail v0.7.1 h1:rvy63sp14N06/kdGqCYwW8Na5gDCXjTQM1E7So4PuKk=
|
||||
github.com/wneessen/go-mail v0.7.1/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
||||
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/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
@ -837,7 +837,6 @@ 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.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
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.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -848,8 +847,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
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-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4=
|
||||
@ -930,9 +929,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.7.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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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=
|
||||
@ -974,7 +972,6 @@ 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.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.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
@ -987,7 +984,6 @@ 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.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.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -1003,9 +999,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.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.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
|
@ -417,10 +417,6 @@ func (pr *PullRequest) GetGitHeadRefName() string {
|
||||
return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index)
|
||||
}
|
||||
|
||||
func (pr *PullRequest) GetGitHeadBranchRefName() string {
|
||||
return fmt.Sprintf("%s%s", git.BranchPrefix, pr.HeadBranch)
|
||||
}
|
||||
|
||||
// GetReviewCommentsCount returns the number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
|
||||
func (pr *PullRequest) GetReviewCommentsCount(ctx context.Context) int {
|
||||
opts := FindCommentsOptions{
|
||||
@ -646,9 +642,8 @@ func (pr *PullRequest) UpdateCols(ctx context.Context, cols ...string) error {
|
||||
}
|
||||
|
||||
// UpdateColsIfNotMerged updates specific fields of a pull request if it has not been merged
|
||||
func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string) error {
|
||||
_, err := db.GetEngine(ctx).Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr)
|
||||
return err
|
||||
func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string) (int64, error) {
|
||||
return db.GetEngine(ctx).Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr)
|
||||
}
|
||||
|
||||
// IsWorkInProgress determine if the Pull Request is a Work In Progress by its title
|
||||
|
@ -534,15 +534,15 @@ func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, revi
|
||||
}
|
||||
|
||||
// GetReviewByIssueIDAndUserID get the latest review of reviewer for a pull request
|
||||
func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64, dismissed ...bool) (*Review, error) {
|
||||
func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64, dismissed optional.Option[bool]) (*Review, error) {
|
||||
review := new(Review)
|
||||
|
||||
cond := builder.In("type", ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
|
||||
And(builder.Eq{"issue_id": issueID, "reviewer_id": userID, "original_author_id": 0})
|
||||
|
||||
// apply optional filter for dismissed
|
||||
if len(dismissed) != 0 {
|
||||
cond = cond.And(builder.Eq{"dismissed": dismissed[0]})
|
||||
if dismissed.Has() {
|
||||
cond = cond.And(builder.Eq{"dismissed": dismissed.Value()})
|
||||
}
|
||||
|
||||
has, err := db.GetEngine(ctx).Where(cond).
|
||||
@ -655,7 +655,7 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo
|
||||
return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) {
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
|
||||
review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID, optional.None[bool]())
|
||||
if err != nil && !IsErrReviewNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
@ -725,7 +725,7 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo
|
||||
// RemoveReviewRequest remove a review request from one reviewer
|
||||
func RemoveReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
|
||||
return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) {
|
||||
review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID, false)
|
||||
review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID, optional.Some(false))
|
||||
if err != nil && !IsErrReviewNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
@ -760,7 +760,7 @@ func RemoveReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user
|
||||
|
||||
// Recalculate the latest official review for reviewer
|
||||
func restoreLatestOfficialReview(ctx context.Context, issueID, reviewerID int64) error {
|
||||
review, err := GetReviewByIssueIDAndUserID(ctx, issueID, reviewerID)
|
||||
review, err := GetReviewByIssueIDAndUserID(ctx, issueID, reviewerID, optional.None[bool]())
|
||||
if err != nil && !IsErrReviewNotExist(err) {
|
||||
return err
|
||||
}
|
||||
@ -852,7 +852,7 @@ func RemoveTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organi
|
||||
|
||||
if official {
|
||||
// recalculate which is the latest official review from that team
|
||||
review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, -reviewer.ID)
|
||||
review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, -reviewer.ID, optional.None[bool]())
|
||||
if err != nil && !IsErrReviewNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -6,11 +6,10 @@ package v1_12
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@ -85,12 +84,9 @@ func AddCommitDivergenceToPulls(x *xorm.Engine) error {
|
||||
log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
|
||||
continue
|
||||
}
|
||||
userPath := filepath.Join(setting.RepoRootPath, strings.ToLower(baseRepo.OwnerName))
|
||||
repoPath := filepath.Join(userPath, strings.ToLower(baseRepo.Name)+".git")
|
||||
|
||||
repoStore := repo_model.StorageRepo(repo_model.RelativePath(baseRepo.OwnerName, baseRepo.Name))
|
||||
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
|
||||
|
||||
divergence, err := git.GetDivergingCommits(graceful.GetManager().HammerContext(), repoPath, pr.BaseBranch, gitRefName)
|
||||
divergence, err := gitrepo.GetDivergingCommits(graceful.GetManager().HammerContext(), repoStore, pr.BaseBranch, gitRefName)
|
||||
if err != nil {
|
||||
log.Warn("Could not recalculate Divergence for pull: %d", pr.ID)
|
||||
pr.CommitsAhead = 0
|
||||
|
@ -377,20 +377,28 @@ func matchIssuesEvent(issuePayload *api.IssuePayload, evt *jobparser.Event) bool
|
||||
// Actions with the same name:
|
||||
// opened, edited, closed, reopened, assigned, unassigned, milestoned, demilestoned
|
||||
// Actions need to be converted:
|
||||
// label_updated -> labeled
|
||||
// label_updated -> labeled (when adding) or unlabeled (when removing)
|
||||
// label_cleared -> unlabeled
|
||||
// Unsupported activity types:
|
||||
// deleted, transferred, pinned, unpinned, locked, unlocked
|
||||
|
||||
action := issuePayload.Action
|
||||
switch action {
|
||||
actions := []string{}
|
||||
switch issuePayload.Action {
|
||||
case api.HookIssueLabelUpdated:
|
||||
action = "labeled"
|
||||
if len(issuePayload.Changes.AddedLabels) > 0 {
|
||||
actions = append(actions, "labeled")
|
||||
}
|
||||
if len(issuePayload.Changes.RemovedLabels) > 0 {
|
||||
actions = append(actions, "unlabeled")
|
||||
}
|
||||
case api.HookIssueLabelCleared:
|
||||
action = "unlabeled"
|
||||
actions = append(actions, "unlabeled")
|
||||
default:
|
||||
actions = append(actions, string(issuePayload.Action))
|
||||
}
|
||||
|
||||
for _, val := range vals {
|
||||
if glob.MustCompile(val, '/').Match(string(action)) {
|
||||
if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
|
||||
matchTimes++
|
||||
break
|
||||
}
|
||||
|
@ -154,3 +154,184 @@ func TestDetectMatched(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchIssuesEvent(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
payload *api.IssuePayload
|
||||
yamlOn string
|
||||
expected bool
|
||||
eventType string
|
||||
}{
|
||||
{
|
||||
desc: "Label deletion should trigger unlabeled event",
|
||||
payload: &api.IssuePayload{
|
||||
Action: api.HookIssueLabelUpdated,
|
||||
Issue: &api.Issue{
|
||||
Labels: []*api.Label{},
|
||||
},
|
||||
Changes: &api.ChangesPayload{
|
||||
RemovedLabels: []*api.Label{
|
||||
{ID: 123, Name: "deleted-label"},
|
||||
},
|
||||
},
|
||||
},
|
||||
yamlOn: "on:\n issues:\n types: [unlabeled]",
|
||||
expected: true,
|
||||
eventType: "unlabeled",
|
||||
},
|
||||
{
|
||||
desc: "Label deletion with existing labels should trigger unlabeled event",
|
||||
payload: &api.IssuePayload{
|
||||
Action: api.HookIssueLabelUpdated,
|
||||
Issue: &api.Issue{
|
||||
Labels: []*api.Label{
|
||||
{ID: 456, Name: "existing-label"},
|
||||
},
|
||||
},
|
||||
Changes: &api.ChangesPayload{
|
||||
AddedLabels: nil,
|
||||
RemovedLabels: []*api.Label{
|
||||
{ID: 123, Name: "deleted-label"},
|
||||
},
|
||||
},
|
||||
},
|
||||
yamlOn: "on:\n issues:\n types: [unlabeled]",
|
||||
expected: true,
|
||||
eventType: "unlabeled",
|
||||
},
|
||||
{
|
||||
desc: "Label addition should trigger labeled event",
|
||||
payload: &api.IssuePayload{
|
||||
Action: api.HookIssueLabelUpdated,
|
||||
Issue: &api.Issue{
|
||||
Labels: []*api.Label{
|
||||
{ID: 123, Name: "new-label"},
|
||||
},
|
||||
},
|
||||
Changes: &api.ChangesPayload{
|
||||
AddedLabels: []*api.Label{
|
||||
{ID: 123, Name: "new-label"},
|
||||
},
|
||||
RemovedLabels: []*api.Label{}, // Empty array, no labels removed
|
||||
},
|
||||
},
|
||||
yamlOn: "on:\n issues:\n types: [labeled]",
|
||||
expected: true,
|
||||
eventType: "labeled",
|
||||
},
|
||||
{
|
||||
desc: "Label clear should trigger unlabeled event",
|
||||
payload: &api.IssuePayload{
|
||||
Action: api.HookIssueLabelCleared,
|
||||
Issue: &api.Issue{
|
||||
Labels: []*api.Label{},
|
||||
},
|
||||
},
|
||||
yamlOn: "on:\n issues:\n types: [unlabeled]",
|
||||
expected: true,
|
||||
eventType: "unlabeled",
|
||||
},
|
||||
{
|
||||
desc: "Both adding and removing labels should trigger labeled event",
|
||||
payload: &api.IssuePayload{
|
||||
Action: api.HookIssueLabelUpdated,
|
||||
Issue: &api.Issue{
|
||||
Labels: []*api.Label{
|
||||
{ID: 789, Name: "new-label"},
|
||||
},
|
||||
},
|
||||
Changes: &api.ChangesPayload{
|
||||
AddedLabels: []*api.Label{
|
||||
{ID: 789, Name: "new-label"},
|
||||
},
|
||||
RemovedLabels: []*api.Label{
|
||||
{ID: 123, Name: "deleted-label"},
|
||||
},
|
||||
},
|
||||
},
|
||||
yamlOn: "on:\n issues:\n types: [labeled]",
|
||||
expected: true,
|
||||
eventType: "labeled",
|
||||
},
|
||||
{
|
||||
desc: "Both adding and removing labels should trigger unlabeled event",
|
||||
payload: &api.IssuePayload{
|
||||
Action: api.HookIssueLabelUpdated,
|
||||
Issue: &api.Issue{
|
||||
Labels: []*api.Label{
|
||||
{ID: 789, Name: "new-label"},
|
||||
},
|
||||
},
|
||||
Changes: &api.ChangesPayload{
|
||||
AddedLabels: []*api.Label{
|
||||
{ID: 789, Name: "new-label"},
|
||||
},
|
||||
RemovedLabels: []*api.Label{
|
||||
{ID: 123, Name: "deleted-label"},
|
||||
},
|
||||
},
|
||||
},
|
||||
yamlOn: "on:\n issues:\n types: [unlabeled]",
|
||||
expected: true,
|
||||
eventType: "unlabeled",
|
||||
},
|
||||
{
|
||||
desc: "Both adding and removing labels should trigger both events",
|
||||
payload: &api.IssuePayload{
|
||||
Action: api.HookIssueLabelUpdated,
|
||||
Issue: &api.Issue{
|
||||
Labels: []*api.Label{
|
||||
{ID: 789, Name: "new-label"},
|
||||
},
|
||||
},
|
||||
Changes: &api.ChangesPayload{
|
||||
AddedLabels: []*api.Label{
|
||||
{ID: 789, Name: "new-label"},
|
||||
},
|
||||
RemovedLabels: []*api.Label{
|
||||
{ID: 123, Name: "deleted-label"},
|
||||
},
|
||||
},
|
||||
},
|
||||
yamlOn: "on:\n issues:\n types: [labeled, unlabeled]",
|
||||
expected: true,
|
||||
eventType: "multiple",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
evts, err := GetEventsFromContent([]byte(tc.yamlOn))
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, evts, 1)
|
||||
|
||||
// Test if the event matches as expected
|
||||
assert.Equal(t, tc.expected, matchIssuesEvent(tc.payload, evts[0]))
|
||||
|
||||
// For extra validation, check that action mapping works correctly
|
||||
if tc.eventType == "multiple" {
|
||||
// Skip direct action mapping validation for multiple events case
|
||||
// as one action can map to multiple event types
|
||||
return
|
||||
}
|
||||
|
||||
// Determine expected action for single event case
|
||||
var expectedAction string
|
||||
switch tc.payload.Action {
|
||||
case api.HookIssueLabelUpdated:
|
||||
if tc.eventType == "labeled" {
|
||||
expectedAction = "labeled"
|
||||
} else if tc.eventType == "unlabeled" {
|
||||
expectedAction = "unlabeled"
|
||||
}
|
||||
case api.HookIssueLabelCleared:
|
||||
expectedAction = "unlabeled"
|
||||
default:
|
||||
expectedAction = string(tc.payload.Action)
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedAction, tc.eventType, "Event type should match expected")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -365,11 +365,11 @@ func GenerateEmbedBindata(fsRootPath, outputFile string) error {
|
||||
if err = embedFiles(meta.Root, fsRootPath, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
jsonBuf, err := json.Marshal(meta) // can't use json.NewEncoder here because it writes extra EOL
|
||||
jsonBuf, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = output.Write([]byte{'\n'})
|
||||
_, err = output.Write(jsonBuf)
|
||||
_, err = output.Write(bytes.TrimSpace(jsonBuf))
|
||||
return err
|
||||
}
|
||||
|
@ -243,36 +243,6 @@ func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error
|
||||
return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime)
|
||||
}
|
||||
|
||||
// DivergeObject represents commit count diverging commits
|
||||
type DivergeObject struct {
|
||||
Ahead int
|
||||
Behind int
|
||||
}
|
||||
|
||||
// GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch
|
||||
func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch string) (do DivergeObject, err error) {
|
||||
cmd := gitcmd.NewCommand("rev-list", "--count", "--left-right").
|
||||
AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--")
|
||||
stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
if err != nil {
|
||||
return do, err
|
||||
}
|
||||
left, right, found := strings.Cut(strings.Trim(stdout, "\n"), "\t")
|
||||
if !found {
|
||||
return do, fmt.Errorf("git rev-list output is missing a tab: %q", stdout)
|
||||
}
|
||||
|
||||
do.Behind, err = strconv.Atoi(left)
|
||||
if err != nil {
|
||||
return do, err
|
||||
}
|
||||
do.Ahead, err = strconv.Atoi(right)
|
||||
if err != nil {
|
||||
return do, err
|
||||
}
|
||||
return do, nil
|
||||
}
|
||||
|
||||
// CreateBundle create bundle content to the target path
|
||||
func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.Writer) error {
|
||||
tmp, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-bundle")
|
||||
|
@ -1,25 +0,0 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
)
|
||||
|
||||
// LineBlame returns the latest commit at the given line
|
||||
func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) {
|
||||
res, _, err := gitcmd.NewCommand("blame").
|
||||
AddOptionFormat("-L %d,%d", line, line).
|
||||
AddOptionValues("-p", revision).
|
||||
AddDashesAndList(file).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(res) < 40 {
|
||||
return nil, fmt.Errorf("invalid result of blame: %s", res)
|
||||
}
|
||||
return repo.GetCommit(res[:40])
|
||||
}
|
@ -5,70 +5,12 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
)
|
||||
|
||||
// BranchPrefix base dir of the branch information file store on git
|
||||
const BranchPrefix = "refs/heads/"
|
||||
|
||||
// IsReferenceExist returns true if given reference exists in the repository.
|
||||
func IsReferenceExist(ctx context.Context, repoPath, name string) bool {
|
||||
_, _, err := gitcmd.NewCommand("show-ref", "--verify").AddDashesAndList(name).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsBranchExist returns true if given branch exists in the repository.
|
||||
func IsBranchExist(ctx context.Context, repoPath, name string) bool {
|
||||
return IsReferenceExist(ctx, repoPath, BranchPrefix+name)
|
||||
}
|
||||
|
||||
func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) {
|
||||
stdout, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stdout = strings.TrimSpace(stdout)
|
||||
if !strings.HasPrefix(stdout, BranchPrefix) {
|
||||
return "", errors.New("the HEAD is not a branch: " + stdout)
|
||||
}
|
||||
return strings.TrimPrefix(stdout, BranchPrefix), nil
|
||||
}
|
||||
|
||||
// DeleteBranchOptions Option(s) for delete branch
|
||||
type DeleteBranchOptions struct {
|
||||
Force bool
|
||||
}
|
||||
|
||||
// DeleteBranch delete a branch by name on repository.
|
||||
func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error {
|
||||
cmd := gitcmd.NewCommand("branch")
|
||||
|
||||
if opts.Force {
|
||||
cmd.AddArguments("-D")
|
||||
} else {
|
||||
cmd.AddArguments("-d")
|
||||
}
|
||||
|
||||
cmd.AddDashesAndList(name)
|
||||
_, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateBranch create a new branch
|
||||
func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error {
|
||||
cmd := gitcmd.NewCommand("branch")
|
||||
cmd.AddDashesAndList(branch, oldbranchOrCommit)
|
||||
|
||||
_, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// AddRemote adds a new remote to repository.
|
||||
func (repo *Repository) AddRemote(name, url string, fetch bool) error {
|
||||
cmd := gitcmd.NewCommand("remote", "add")
|
||||
@ -80,9 +22,3 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error {
|
||||
_, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
|
||||
return err
|
||||
}
|
||||
|
||||
// RenameBranch rename a branch
|
||||
func (repo *Repository) RenameBranch(from, to string) error {
|
||||
_, _, err := gitcmd.NewCommand("branch", "-m").AddDynamicArguments(from, to).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
|
||||
return err
|
||||
}
|
||||
|
@ -7,14 +7,12 @@ package git
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
@ -87,57 +85,8 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
|
||||
return w.numLines, nil
|
||||
}
|
||||
|
||||
// GetDiffShortStatByCmdArgs counts number of changed files, number of additions and deletions
|
||||
// TODO: it can be merged with another "GetDiffShortStat" in the future
|
||||
func GetDiffShortStatByCmdArgs(ctx context.Context, repoPath string, trustedArgs gitcmd.TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) {
|
||||
// Now if we call:
|
||||
// $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875
|
||||
// we get:
|
||||
// " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n"
|
||||
cmd := gitcmd.NewCommand("diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...)
|
||||
stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
return parseDiffStat(stdout)
|
||||
}
|
||||
|
||||
var shortStatFormat = regexp.MustCompile(
|
||||
`\s*(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?`)
|
||||
|
||||
var patchCommits = regexp.MustCompile(`^From\s(\w+)\s`)
|
||||
|
||||
func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int, err error) {
|
||||
if len(stdout) == 0 || stdout == "\n" {
|
||||
return 0, 0, 0, nil
|
||||
}
|
||||
groups := shortStatFormat.FindStringSubmatch(stdout)
|
||||
if len(groups) != 4 {
|
||||
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s groups: %s", stdout, groups)
|
||||
}
|
||||
|
||||
numFiles, err = strconv.Atoi(groups[1])
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumFiles %w", stdout, err)
|
||||
}
|
||||
|
||||
if len(groups[2]) != 0 {
|
||||
totalAdditions, err = strconv.Atoi(groups[2])
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumAdditions %w", stdout, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(groups[3]) != 0 {
|
||||
totalDeletions, err = strconv.Atoi(groups[3])
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumDeletions %w", stdout, err)
|
||||
}
|
||||
}
|
||||
return numFiles, totalAdditions, totalDeletions, err
|
||||
}
|
||||
|
||||
// GetDiff generates and returns patch data between given revisions, optimized for human readability
|
||||
func (repo *Repository) GetDiff(compareArg string, w io.Writer) error {
|
||||
stderr := new(bytes.Buffer)
|
||||
|
@ -29,27 +29,3 @@ func TestRepoIsEmpty(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, isEmpty)
|
||||
}
|
||||
|
||||
func TestRepoGetDivergingCommits(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
do, err := GetDivergingCommits(t.Context(), bareRepo1Path, "master", "branch2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, DivergeObject{
|
||||
Ahead: 1,
|
||||
Behind: 5,
|
||||
}, do)
|
||||
|
||||
do, err = GetDivergingCommits(t.Context(), bareRepo1Path, "master", "master")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, DivergeObject{
|
||||
Ahead: 0,
|
||||
Behind: 0,
|
||||
}, do)
|
||||
|
||||
do, err = GetDivergingCommits(t.Context(), bareRepo1Path, "master", "test")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, DivergeObject{
|
||||
Ahead: 0,
|
||||
Behind: 2,
|
||||
}, do)
|
||||
}
|
||||
|
18
modules/gitrepo/blame.go
Normal file
18
modules/gitrepo/blame.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gitrepo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
)
|
||||
|
||||
func LineBlame(ctx context.Context, repo Repository, revision, file string, line uint) (string, error) {
|
||||
return runCmdString(ctx, repo,
|
||||
gitcmd.NewCommand("blame").
|
||||
AddOptionFormat("-L %d,%d", line, line).
|
||||
AddOptionValues("-p", revision).
|
||||
AddDashesAndList(file))
|
||||
}
|
@ -5,6 +5,8 @@ package gitrepo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
@ -34,23 +36,61 @@ func GetBranchCommitID(ctx context.Context, repo Repository, branch string) (str
|
||||
|
||||
// SetDefaultBranch sets default branch of repository.
|
||||
func SetDefaultBranch(ctx context.Context, repo Repository, name string) error {
|
||||
_, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD").
|
||||
AddDynamicArguments(git.BranchPrefix+name).
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
|
||||
_, err := runCmdString(ctx, repo, gitcmd.NewCommand("symbolic-ref", "HEAD").
|
||||
AddDynamicArguments(git.BranchPrefix+name))
|
||||
return err
|
||||
}
|
||||
|
||||
// GetDefaultBranch gets default branch of repository.
|
||||
func GetDefaultBranch(ctx context.Context, repo Repository) (string, error) {
|
||||
return git.GetDefaultBranch(ctx, repoPath(repo))
|
||||
stdout, err := runCmdString(ctx, repo, gitcmd.NewCommand("symbolic-ref", "HEAD"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stdout = strings.TrimSpace(stdout)
|
||||
if !strings.HasPrefix(stdout, git.BranchPrefix) {
|
||||
return "", errors.New("the HEAD is not a branch: " + stdout)
|
||||
}
|
||||
return strings.TrimPrefix(stdout, git.BranchPrefix), nil
|
||||
}
|
||||
|
||||
// IsReferenceExist returns true if given reference exists in the repository.
|
||||
func IsReferenceExist(ctx context.Context, repo Repository, name string) bool {
|
||||
return git.IsReferenceExist(ctx, repoPath(repo), name)
|
||||
_, err := runCmdString(ctx, repo, gitcmd.NewCommand("show-ref", "--verify").AddDashesAndList(name))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsBranchExist returns true if given branch exists in the repository.
|
||||
func IsBranchExist(ctx context.Context, repo Repository, name string) bool {
|
||||
return IsReferenceExist(ctx, repo, git.BranchPrefix+name)
|
||||
}
|
||||
|
||||
// DeleteBranch delete a branch by name on repository.
|
||||
func DeleteBranch(ctx context.Context, repo Repository, name string, force bool) error {
|
||||
cmd := gitcmd.NewCommand("branch")
|
||||
|
||||
if force {
|
||||
cmd.AddArguments("-D")
|
||||
} else {
|
||||
cmd.AddArguments("-d")
|
||||
}
|
||||
|
||||
cmd.AddDashesAndList(name)
|
||||
_, err := runCmdString(ctx, repo, cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateBranch create a new branch
|
||||
func CreateBranch(ctx context.Context, repo Repository, branch, oldbranchOrCommit string) error {
|
||||
cmd := gitcmd.NewCommand("branch")
|
||||
cmd.AddDashesAndList(branch, oldbranchOrCommit)
|
||||
|
||||
_, err := runCmdString(ctx, repo, cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// RenameBranch rename a branch
|
||||
func RenameBranch(ctx context.Context, repo Repository, from, to string) error {
|
||||
_, err := runCmdString(ctx, repo, gitcmd.NewCommand("branch", "-m").AddDynamicArguments(from, to))
|
||||
return err
|
||||
}
|
||||
|
15
modules/gitrepo/command.go
Normal file
15
modules/gitrepo/command.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gitrepo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
)
|
||||
|
||||
func runCmdString(ctx context.Context, repo Repository, cmd *gitcmd.Command) (string, error) {
|
||||
res, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
|
||||
return res, err
|
||||
}
|
44
modules/gitrepo/compare.go
Normal file
44
modules/gitrepo/compare.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gitrepo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
)
|
||||
|
||||
// DivergeObject represents commit count diverging commits
|
||||
type DivergeObject struct {
|
||||
Ahead int
|
||||
Behind int
|
||||
}
|
||||
|
||||
// GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch
|
||||
func GetDivergingCommits(ctx context.Context, repo Repository, baseBranch, targetBranch string) (*DivergeObject, error) {
|
||||
cmd := gitcmd.NewCommand("rev-list", "--count", "--left-right").
|
||||
AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--")
|
||||
stdout, _, err1 := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
|
||||
if err1 != nil {
|
||||
return nil, err1
|
||||
}
|
||||
|
||||
left, right, found := strings.Cut(strings.Trim(stdout, "\n"), "\t")
|
||||
if !found {
|
||||
return nil, fmt.Errorf("git rev-list output is missing a tab: %q", stdout)
|
||||
}
|
||||
|
||||
behind, err := strconv.Atoi(left)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ahead, err := strconv.Atoi(right)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DivergeObject{Ahead: ahead, Behind: behind}, nil
|
||||
}
|
42
modules/gitrepo/compare_test.go
Normal file
42
modules/gitrepo/compare_test.go
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gitrepo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockRepository struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (r *mockRepository) RelativePath() string {
|
||||
return r.path
|
||||
}
|
||||
|
||||
func TestRepoGetDivergingCommits(t *testing.T) {
|
||||
repo := &mockRepository{path: "repo1_bare"}
|
||||
do, err := GetDivergingCommits(t.Context(), repo, "master", "branch2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &DivergeObject{
|
||||
Ahead: 1,
|
||||
Behind: 5,
|
||||
}, do)
|
||||
|
||||
do, err = GetDivergingCommits(t.Context(), repo, "master", "master")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &DivergeObject{
|
||||
Ahead: 0,
|
||||
Behind: 0,
|
||||
}, do)
|
||||
|
||||
do, err = GetDivergingCommits(t.Context(), repo, "master", "test")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &DivergeObject{
|
||||
Ahead: 0,
|
||||
Behind: 2,
|
||||
}, do)
|
||||
}
|
@ -12,9 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func GitConfigGet(ctx context.Context, repo Repository, key string) (string, error) {
|
||||
result, _, err := gitcmd.NewCommand("config", "--get").
|
||||
AddDynamicArguments(key).
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
|
||||
result, err := runCmdString(ctx, repo, gitcmd.NewCommand("config", "--get").
|
||||
AddDynamicArguments(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -28,9 +27,8 @@ func getRepoConfigLockKey(repoStoragePath string) string {
|
||||
// GitConfigAdd add a git configuration key to a specific value for the given repository.
|
||||
func GitConfigAdd(ctx context.Context, repo Repository, key, value string) error {
|
||||
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
|
||||
_, _, err := gitcmd.NewCommand("config", "--add").
|
||||
AddDynamicArguments(key, value).
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
|
||||
_, err := runCmdString(ctx, repo, gitcmd.NewCommand("config", "--add").
|
||||
AddDynamicArguments(key, value))
|
||||
return err
|
||||
})
|
||||
}
|
||||
@ -40,9 +38,8 @@ func GitConfigAdd(ctx context.Context, repo Repository, key, value string) error
|
||||
// If the key exists, it will be updated to the new value.
|
||||
func GitConfigSet(ctx context.Context, repo Repository, key, value string) error {
|
||||
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
|
||||
_, _, err := gitcmd.NewCommand("config").
|
||||
AddDynamicArguments(key, value).
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
|
||||
_, err := runCmdString(ctx, repo, gitcmd.NewCommand("config").
|
||||
AddDynamicArguments(key, value))
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
62
modules/gitrepo/diff.go
Normal file
62
modules/gitrepo/diff.go
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gitrepo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
)
|
||||
|
||||
// GetDiffShortStatByCmdArgs counts number of changed files, number of additions and deletions
|
||||
// TODO: it can be merged with another "GetDiffShortStat" in the future
|
||||
func GetDiffShortStatByCmdArgs(ctx context.Context, repo Repository, trustedArgs gitcmd.TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) {
|
||||
// Now if we call:
|
||||
// $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875
|
||||
// we get:
|
||||
// " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n"
|
||||
cmd := gitcmd.NewCommand("diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...)
|
||||
stdout, err := runCmdString(ctx, repo, cmd)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
return parseDiffStat(stdout)
|
||||
}
|
||||
|
||||
var shortStatFormat = regexp.MustCompile(
|
||||
`\s*(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?`)
|
||||
|
||||
func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int, err error) {
|
||||
if len(stdout) == 0 || stdout == "\n" {
|
||||
return 0, 0, 0, nil
|
||||
}
|
||||
groups := shortStatFormat.FindStringSubmatch(stdout)
|
||||
if len(groups) != 4 {
|
||||
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s groups: %s", stdout, groups)
|
||||
}
|
||||
|
||||
numFiles, err = strconv.Atoi(groups[1])
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumFiles %w", stdout, err)
|
||||
}
|
||||
|
||||
if len(groups[2]) != 0 {
|
||||
totalAdditions, err = strconv.Atoi(groups[2])
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumAdditions %w", stdout, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(groups[3]) != 0 {
|
||||
totalDeletions, err = strconv.Atoi(groups[3])
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumDeletions %w", stdout, err)
|
||||
}
|
||||
}
|
||||
return numFiles, totalAdditions, totalDeletions, err
|
||||
}
|
@ -20,9 +20,9 @@ type Repository interface {
|
||||
RelativePath() string // We don't assume how the directory structure of the repository is, so we only need the relative path
|
||||
}
|
||||
|
||||
// RelativePath should be an unix style path like username/reponame.git
|
||||
// This method should change it according to the current OS.
|
||||
func repoPath(repo Repository) string {
|
||||
// repoPath resolves the Repository.RelativePath (which is a unix-style path like "username/reponame.git")
|
||||
// to a local filesystem path according to setting.RepoRootPath
|
||||
var repoPath = func(repo Repository) string {
|
||||
return filepath.Join(setting.RepoRootPath, filepath.FromSlash(repo.RelativePath()))
|
||||
}
|
||||
|
||||
|
32
modules/gitrepo/main_test.go
Normal file
32
modules/gitrepo/main_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gitrepo
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/tempdir"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
gitHomePath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("git-home")
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create temp dir: %v", err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// resolve repository path relative to the test directory
|
||||
testRootDir := test.SetupGiteaRoot()
|
||||
repoPath = func(repo Repository) string {
|
||||
return filepath.Join(testRootDir, "/modules/git/tests/repos", repo.RelativePath())
|
||||
}
|
||||
|
||||
setting.Git.HomePath = gitHomePath
|
||||
os.Exit(m.Run())
|
||||
}
|
@ -36,9 +36,7 @@ func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL st
|
||||
return errors.New("unknown remote option: " + string(options[0]))
|
||||
}
|
||||
}
|
||||
_, _, err := cmd.
|
||||
AddDynamicArguments(remoteName, remoteURL).
|
||||
RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
|
||||
_, err := runCmdString(ctx, repo, cmd.AddDynamicArguments(remoteName, remoteURL))
|
||||
return err
|
||||
})
|
||||
}
|
||||
@ -46,7 +44,7 @@ func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL st
|
||||
func GitRemoteRemove(ctx context.Context, repo Repository, remoteName string) error {
|
||||
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
|
||||
cmd := gitcmd.NewCommand("remote", "rm").AddDynamicArguments(remoteName)
|
||||
_, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)})
|
||||
_, err := runCmdString(ctx, repo, cmd)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/json" //nolint:depguard // this package wraps it
|
||||
"io"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
// Encoder represents an encoder for json
|
||||
@ -31,71 +29,7 @@ type Interface interface {
|
||||
Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultJSONHandler default json handler
|
||||
DefaultJSONHandler Interface = JSONiter{jsoniter.ConfigCompatibleWithStandardLibrary}
|
||||
|
||||
_ Interface = StdJSON{}
|
||||
_ Interface = JSONiter{}
|
||||
)
|
||||
|
||||
// StdJSON implements Interface via encoding/json
|
||||
type StdJSON struct{}
|
||||
|
||||
// Marshal implements Interface
|
||||
func (StdJSON) Marshal(v any) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// Unmarshal implements Interface
|
||||
func (StdJSON) Unmarshal(data []byte, v any) error {
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
// NewEncoder implements Interface
|
||||
func (StdJSON) NewEncoder(writer io.Writer) Encoder {
|
||||
return json.NewEncoder(writer)
|
||||
}
|
||||
|
||||
// NewDecoder implements Interface
|
||||
func (StdJSON) NewDecoder(reader io.Reader) Decoder {
|
||||
return json.NewDecoder(reader)
|
||||
}
|
||||
|
||||
// Indent implements Interface
|
||||
func (StdJSON) Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||
return json.Indent(dst, src, prefix, indent)
|
||||
}
|
||||
|
||||
// JSONiter implements Interface via jsoniter
|
||||
type JSONiter struct {
|
||||
jsoniter.API
|
||||
}
|
||||
|
||||
// Marshal implements Interface
|
||||
func (j JSONiter) Marshal(v any) ([]byte, error) {
|
||||
return j.API.Marshal(v)
|
||||
}
|
||||
|
||||
// Unmarshal implements Interface
|
||||
func (j JSONiter) Unmarshal(data []byte, v any) error {
|
||||
return j.API.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
// NewEncoder implements Interface
|
||||
func (j JSONiter) NewEncoder(writer io.Writer) Encoder {
|
||||
return j.API.NewEncoder(writer)
|
||||
}
|
||||
|
||||
// NewDecoder implements Interface
|
||||
func (j JSONiter) NewDecoder(reader io.Reader) Decoder {
|
||||
return j.API.NewDecoder(reader)
|
||||
}
|
||||
|
||||
// Indent implements Interface, since jsoniter don't support Indent, just use encoding/json's
|
||||
func (j JSONiter) Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||
return json.Indent(dst, src, prefix, indent)
|
||||
}
|
||||
var DefaultJSONHandler = getDefaultJSONHandler()
|
||||
|
||||
// Marshal converts object as bytes
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -16,3 +17,12 @@ func TestGiteaDBJSONUnmarshal(t *testing.T) {
|
||||
err = UnmarshalHandleDoubleEncode([]byte(""), &m)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIndent(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
err := Indent(buf, []byte(`{"a":1}`), ">", " ")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{
|
||||
> "a": 1
|
||||
>}`, buf.String())
|
||||
}
|
||||
|
35
modules/json/jsongoccy.go
Normal file
35
modules/json/jsongoccy.go
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
var _ Interface = jsonGoccy{}
|
||||
|
||||
type jsonGoccy struct{}
|
||||
|
||||
func (jsonGoccy) Marshal(v any) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
func (jsonGoccy) Unmarshal(data []byte, v any) error {
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
func (jsonGoccy) NewEncoder(writer io.Writer) Encoder {
|
||||
return json.NewEncoder(writer)
|
||||
}
|
||||
|
||||
func (jsonGoccy) NewDecoder(reader io.Reader) Decoder {
|
||||
return json.NewDecoder(reader)
|
||||
}
|
||||
|
||||
func (jsonGoccy) Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||
return json.Indent(dst, src, prefix, indent)
|
||||
}
|
22
modules/json/jsonlegacy.go
Normal file
22
modules/json/jsonlegacy.go
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build !goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func getDefaultJSONHandler() Interface {
|
||||
return jsonGoccy{}
|
||||
}
|
||||
|
||||
func MarshalKeepOptionalEmpty(v any) ([]byte, error) {
|
||||
return DefaultJSONHandler.Marshal(v)
|
||||
}
|
||||
|
||||
func NewDecoderCaseInsensitive(reader io.Reader) Decoder {
|
||||
return DefaultJSONHandler.NewDecoder(reader)
|
||||
}
|
34
modules/json/jsonv1.go
Normal file
34
modules/json/jsonv1.go
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json" //nolint:depguard // this package wraps it
|
||||
"io"
|
||||
)
|
||||
|
||||
type jsonV1 struct{}
|
||||
|
||||
var _ Interface = jsonV1{}
|
||||
|
||||
func (jsonV1) Marshal(v any) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
func (jsonV1) Unmarshal(data []byte, v any) error {
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
func (jsonV1) NewEncoder(writer io.Writer) Encoder {
|
||||
return json.NewEncoder(writer)
|
||||
}
|
||||
|
||||
func (jsonV1) NewDecoder(reader io.Reader) Decoder {
|
||||
return json.NewDecoder(reader)
|
||||
}
|
||||
|
||||
func (jsonV1) Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||
return json.Indent(dst, src, prefix, indent)
|
||||
}
|
92
modules/json/jsonv2.go
Normal file
92
modules/json/jsonv2.go
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build goexperiment.jsonv2
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
jsonv1 "encoding/json" //nolint:depguard // this package wraps it
|
||||
jsonv2 "encoding/json/v2" //nolint:depguard // this package wraps it
|
||||
"io"
|
||||
)
|
||||
|
||||
// JSONv2 implements Interface via encoding/json/v2
|
||||
// Requires GOEXPERIMENT=jsonv2 to be set at build time
|
||||
type JSONv2 struct {
|
||||
marshalOptions jsonv2.Options
|
||||
marshalKeepOptionalEmptyOptions jsonv2.Options
|
||||
unmarshalOptions jsonv2.Options
|
||||
unmarshalCaseInsensitiveOptions jsonv2.Options
|
||||
}
|
||||
|
||||
var jsonV2 JSONv2
|
||||
|
||||
func init() {
|
||||
commonMarshalOptions := []jsonv2.Options{
|
||||
jsonv2.FormatNilSliceAsNull(true),
|
||||
jsonv2.FormatNilMapAsNull(true),
|
||||
}
|
||||
jsonV2.marshalOptions = jsonv2.JoinOptions(commonMarshalOptions...)
|
||||
jsonV2.unmarshalOptions = jsonv2.DefaultOptionsV2()
|
||||
|
||||
// By default, "json/v2" omitempty removes all `""` empty strings, no matter where it comes from.
|
||||
// v1 has a different behavior: if the `""` is from a null pointer, or a Marshal function, it is kept.
|
||||
// Golang issue: https://github.com/golang/go/issues/75623 encoding/json/v2: unable to make omitempty work with pointer or Optional type with goexperiment.jsonv2
|
||||
jsonV2.marshalKeepOptionalEmptyOptions = jsonv2.JoinOptions(append(commonMarshalOptions, jsonv1.OmitEmptyWithLegacySemantics(true))...)
|
||||
|
||||
// Some legacy code uses case-insensitive matching (for example: parsing oci.ImageConfig)
|
||||
jsonV2.unmarshalCaseInsensitiveOptions = jsonv2.JoinOptions(jsonv2.MatchCaseInsensitiveNames(true))
|
||||
}
|
||||
|
||||
func getDefaultJSONHandler() Interface {
|
||||
return &jsonV2
|
||||
}
|
||||
|
||||
func MarshalKeepOptionalEmpty(v any) ([]byte, error) {
|
||||
return jsonv2.Marshal(v, jsonV2.marshalKeepOptionalEmptyOptions)
|
||||
}
|
||||
|
||||
func (j *JSONv2) Marshal(v any) ([]byte, error) {
|
||||
return jsonv2.Marshal(v, j.marshalOptions)
|
||||
}
|
||||
|
||||
func (j *JSONv2) Unmarshal(data []byte, v any) error {
|
||||
return jsonv2.Unmarshal(data, v, j.unmarshalOptions)
|
||||
}
|
||||
|
||||
func (j *JSONv2) NewEncoder(writer io.Writer) Encoder {
|
||||
return &jsonV2Encoder{writer: writer, opts: j.marshalOptions}
|
||||
}
|
||||
|
||||
func (j *JSONv2) NewDecoder(reader io.Reader) Decoder {
|
||||
return &jsonV2Decoder{reader: reader, opts: j.unmarshalOptions}
|
||||
}
|
||||
|
||||
// Indent implements Interface using standard library (JSON v2 doesn't have Indent yet)
|
||||
func (*JSONv2) Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||
return jsonv1.Indent(dst, src, prefix, indent)
|
||||
}
|
||||
|
||||
type jsonV2Encoder struct {
|
||||
writer io.Writer
|
||||
opts jsonv2.Options
|
||||
}
|
||||
|
||||
func (e *jsonV2Encoder) Encode(v any) error {
|
||||
return jsonv2.MarshalWrite(e.writer, v, e.opts)
|
||||
}
|
||||
|
||||
type jsonV2Decoder struct {
|
||||
reader io.Reader
|
||||
opts jsonv2.Options
|
||||
}
|
||||
|
||||
func (d *jsonV2Decoder) Decode(v any) error {
|
||||
return jsonv2.UnmarshalRead(d.reader, v, d.opts)
|
||||
}
|
||||
|
||||
func NewDecoderCaseInsensitive(reader io.Reader) Decoder {
|
||||
return &jsonV2Decoder{reader: reader, opts: jsonV2.unmarshalCaseInsensitiveOptions}
|
||||
}
|
@ -193,7 +193,7 @@ func TestHTTPClientDownload(t *testing.T) {
|
||||
},
|
||||
{
|
||||
endpoint: "https://invalid-json-response.io",
|
||||
expectedError: "invalid json",
|
||||
expectedError: "/(invalid json|invalid character)/",
|
||||
},
|
||||
{
|
||||
endpoint: "https://valid-batch-request-download.io",
|
||||
@ -258,7 +258,11 @@ func TestHTTPClientDownload(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
if c.expectedError != "" {
|
||||
assert.ErrorContains(t, err, c.expectedError)
|
||||
if strings.HasPrefix(c.expectedError, "/") && strings.HasSuffix(c.expectedError, "/") {
|
||||
assert.Regexp(t, strings.Trim(c.expectedError, "/"), err.Error())
|
||||
} else {
|
||||
assert.ErrorContains(t, err, c.expectedError)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@ -297,7 +301,7 @@ func TestHTTPClientUpload(t *testing.T) {
|
||||
},
|
||||
{
|
||||
endpoint: "https://invalid-json-response.io",
|
||||
expectedError: "invalid json",
|
||||
expectedError: "/(invalid json|invalid character)/",
|
||||
},
|
||||
{
|
||||
endpoint: "https://valid-batch-request-upload.io",
|
||||
@ -352,7 +356,11 @@ func TestHTTPClientUpload(t *testing.T) {
|
||||
return io.NopCloser(new(bytes.Buffer)), objectError
|
||||
})
|
||||
if c.expectedError != "" {
|
||||
assert.ErrorContains(t, err, c.expectedError)
|
||||
if strings.HasPrefix(c.expectedError, "/") && strings.HasSuffix(c.expectedError, "/") {
|
||||
assert.Regexp(t, strings.Trim(c.expectedError, "/"), err.Error())
|
||||
} else {
|
||||
assert.ErrorContains(t, err, c.expectedError)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -15,12 +15,17 @@ import (
|
||||
)
|
||||
|
||||
type testSerializationStruct struct {
|
||||
NormalString string `json:"normal_string" yaml:"normal_string"`
|
||||
NormalBool bool `json:"normal_bool" yaml:"normal_bool"`
|
||||
OptBool optional.Option[bool] `json:"optional_bool,omitempty" yaml:"optional_bool,omitempty"`
|
||||
OptString optional.Option[string] `json:"optional_string,omitempty" yaml:"optional_string,omitempty"`
|
||||
NormalString string `json:"normal_string" yaml:"normal_string"`
|
||||
NormalBool bool `json:"normal_bool" yaml:"normal_bool"`
|
||||
OptBool optional.Option[bool] `json:"optional_bool,omitempty" yaml:"optional_bool,omitempty"`
|
||||
|
||||
// It causes an undefined behavior: should the "omitempty" tag only omit "null", or also the empty string?
|
||||
// The behavior is inconsistent between json and v2 packages, and there is no such use case in Gitea.
|
||||
// If anyone really needs it, they can use json.MarshalKeepOptionalEmpty to revert the v1 behavior
|
||||
OptString optional.Option[string] `json:"optional_string,omitempty" yaml:"optional_string,omitempty"`
|
||||
|
||||
OptTwoBool optional.Option[bool] `json:"optional_two_bool" yaml:"optional_two_bool"`
|
||||
OptTwoString optional.Option[string] `json:"optional_twostring" yaml:"optional_two_string"`
|
||||
OptTwoString optional.Option[string] `json:"optional_two_string" yaml:"optional_two_string"`
|
||||
}
|
||||
|
||||
func TestOptionalToJson(t *testing.T) {
|
||||
@ -32,7 +37,7 @@ func TestOptionalToJson(t *testing.T) {
|
||||
{
|
||||
name: "empty",
|
||||
obj: new(testSerializationStruct),
|
||||
want: `{"normal_string":"","normal_bool":false,"optional_two_bool":null,"optional_twostring":null}`,
|
||||
want: `{"normal_string":"","normal_bool":false,"optional_two_bool":null,"optional_two_string":null}`,
|
||||
},
|
||||
{
|
||||
name: "some",
|
||||
@ -44,12 +49,12 @@ func TestOptionalToJson(t *testing.T) {
|
||||
OptTwoBool: optional.None[bool](),
|
||||
OptTwoString: optional.None[string](),
|
||||
},
|
||||
want: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_twostring":null}`,
|
||||
want: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_two_string":null}`,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
b, err := json.Marshal(tc.obj)
|
||||
b, err := json.MarshalKeepOptionalEmpty(tc.obj)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.want, string(b), "gitea json module returned unexpected")
|
||||
|
||||
@ -75,7 +80,7 @@ func TestOptionalFromJson(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "some",
|
||||
data: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_twostring":null}`,
|
||||
data: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_two_string":null}`,
|
||||
want: testSerializationStruct{
|
||||
NormalString: "a string",
|
||||
NormalBool: true,
|
||||
@ -169,7 +174,7 @@ normal_bool: true
|
||||
optional_bool: false
|
||||
optional_string: ""
|
||||
optional_two_bool: null
|
||||
optional_twostring: null
|
||||
optional_two_string: null
|
||||
`,
|
||||
want: testSerializationStruct{
|
||||
NormalString: "a string",
|
||||
|
@ -103,7 +103,9 @@ func ParseImageConfig(mediaType string, r io.Reader) (*Metadata, error) {
|
||||
|
||||
func parseOCIImageConfig(r io.Reader) (*Metadata, error) {
|
||||
var image oci.Image
|
||||
if err := json.NewDecoder(r).Decode(&image); err != nil {
|
||||
// FIXME: JSON-KEY-CASE: here seems a abuse of the case-insensitive decoding feature, spec is case-sensitive
|
||||
// https://github.com/opencontainers/image-spec/blob/main/schema/config-schema.json
|
||||
if err := json.NewDecoderCaseInsensitive(r).Decode(&image); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,8 @@ func TestParseImageConfig(t *testing.T) {
|
||||
repositoryURL := "https://gitea.com/gitea"
|
||||
documentationURL := "https://docs.gitea.com"
|
||||
|
||||
// FIXME: JSON-KEY-CASE: the test case is not right, the config fields are capitalized in the spec
|
||||
// https://github.com/opencontainers/image-spec/blob/main/schema/config-schema.json
|
||||
configOCI := `{"config": {"labels": {"` + labelAuthors + `": "` + author + `", "` + labelLicenses + `": "` + license + `", "` + labelURL + `": "` + projectURL + `", "` + labelSource + `": "` + repositoryURL + `", "` + labelDocumentation + `": "` + documentationURL + `", "` + labelDescription + `": "` + description + `"}}, "history": [{"created_by": "do it 1"}, {"created_by": "dummy #(nop) do it 2"}]}`
|
||||
|
||||
metadata, err := ParseImageConfig(oci.MediaTypeImageManifest, strings.NewReader(configOCI))
|
||||
|
@ -5,6 +5,7 @@ package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
@ -121,12 +122,12 @@ func (p *DBProvider) Read(sid string) (session.RawStore, error) {
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *DBProvider) Exist(sid string) bool {
|
||||
func (p *DBProvider) Exist(sid string) (bool, error) {
|
||||
has, err := auth.ExistSession(dbContext(), sid)
|
||||
if err != nil {
|
||||
panic("session/DB: error checking existence: " + err.Error())
|
||||
return false, fmt.Errorf("session/DB: error checking existence: %w", err)
|
||||
}
|
||||
return has
|
||||
return has, nil
|
||||
}
|
||||
|
||||
// Destroy deletes a session by session ID.
|
||||
@ -155,12 +156,12 @@ func (p *DBProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err err
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *DBProvider) Count() int {
|
||||
func (p *DBProvider) Count() (int, error) {
|
||||
total, err := auth.CountSessions(dbContext())
|
||||
if err != nil {
|
||||
panic("session/DB: error counting records: " + err.Error())
|
||||
return 0, fmt.Errorf("session/DB: error counting records: %w", err)
|
||||
}
|
||||
return int(total)
|
||||
return int(total), nil
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
|
@ -135,10 +135,12 @@ func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) {
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
|
||||
psid := p.prefix + sid
|
||||
if !p.Exist(sid) {
|
||||
if exist, err := p.Exist(sid); err == nil && !exist {
|
||||
if err := p.c.Set(graceful.GetManager().HammerContext(), psid, "", p.duration).Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[any]any
|
||||
@ -159,9 +161,9 @@ func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *RedisProvider) Exist(sid string) bool {
|
||||
func (p *RedisProvider) Exist(sid string) (bool, error) {
|
||||
v, err := p.c.Exists(graceful.GetManager().HammerContext(), p.prefix+sid).Result()
|
||||
return err == nil && v == 1
|
||||
return err == nil && v == 1, err
|
||||
}
|
||||
|
||||
// Destroy deletes a session by session ID.
|
||||
@ -174,13 +176,18 @@ func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err
|
||||
poldsid := p.prefix + oldsid
|
||||
psid := p.prefix + sid
|
||||
|
||||
if p.Exist(sid) {
|
||||
if exist, err := p.Exist(sid); err != nil {
|
||||
return nil, err
|
||||
} else if exist {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
} else if !p.Exist(oldsid) {
|
||||
}
|
||||
if exist, err := p.Exist(oldsid); err == nil && !exist {
|
||||
// Make a fake old session.
|
||||
if err = p.c.Set(graceful.GetManager().HammerContext(), poldsid, "", p.duration).Err(); err != nil {
|
||||
if err := p.c.Set(graceful.GetManager().HammerContext(), poldsid, "", p.duration).Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// do not use Rename here, because the old sid and new sid may be in different redis cluster slot.
|
||||
@ -211,12 +218,9 @@ func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *RedisProvider) Count() int {
|
||||
func (p *RedisProvider) Count() (int, error) {
|
||||
size, err := p.c.DBSize(graceful.GetManager().HammerContext()).Result()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return int(size)
|
||||
return int(size), err
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
|
@ -59,8 +59,10 @@ func (o *VirtualSessionProvider) Init(gcLifetime int64, config string) error {
|
||||
func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) {
|
||||
o.lock.RLock()
|
||||
defer o.lock.RUnlock()
|
||||
if o.provider.Exist(sid) {
|
||||
if exist, err := o.provider.Exist(sid); err == nil && exist {
|
||||
return o.provider.Read(sid)
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("check if '%s' exist failed: %w", sid, err)
|
||||
}
|
||||
kv := make(map[any]any)
|
||||
kv["_old_uid"] = "0"
|
||||
@ -68,8 +70,8 @@ func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) {
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (o *VirtualSessionProvider) Exist(sid string) bool {
|
||||
return true
|
||||
func (o *VirtualSessionProvider) Exist(sid string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Destroy deletes a session by session ID.
|
||||
@ -87,7 +89,7 @@ func (o *VirtualSessionProvider) Regenerate(oldsid, sid string) (session.RawStor
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (o *VirtualSessionProvider) Count() int {
|
||||
func (o *VirtualSessionProvider) Count() (int, error) {
|
||||
o.lock.RLock()
|
||||
defer o.lock.RUnlock()
|
||||
return o.provider.Count()
|
||||
@ -162,9 +164,13 @@ func (s *VirtualStore) Release() error {
|
||||
// Now ensure that we don't exist!
|
||||
realProvider := s.p.provider
|
||||
|
||||
if !s.released && realProvider.Exist(s.sid) {
|
||||
// This is an error!
|
||||
return fmt.Errorf("new sid '%s' already exists", s.sid)
|
||||
if !s.released {
|
||||
if exist, err := realProvider.Exist(s.sid); err == nil && exist {
|
||||
// This is an error!
|
||||
return fmt.Errorf("new sid '%s' already exists", s.sid)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("check if '%s' exist failed: %w", s.sid, err)
|
||||
}
|
||||
}
|
||||
realStore, err := realProvider.Read(s.sid)
|
||||
if err != nil {
|
||||
|
@ -418,6 +418,10 @@ type ChangesPayload struct {
|
||||
Body *ChangesFromPayload `json:"body,omitempty"`
|
||||
// Changes made to the reference
|
||||
Ref *ChangesFromPayload `json:"ref,omitempty"`
|
||||
// Changes made to the labels added
|
||||
AddedLabels []*Label `json:"added_labels"`
|
||||
// Changes made to the labels removed
|
||||
RemovedLabels []*Label `json:"removed_labels"`
|
||||
}
|
||||
|
||||
// PullRequestPayload represents a payload information of pull request event.
|
||||
|
@ -1,57 +0,0 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sync
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
)
|
||||
|
||||
// StatusTable is a table maintains true/false values.
|
||||
//
|
||||
// This table is particularly useful for un/marking and checking values
|
||||
// in different goroutines.
|
||||
type StatusTable struct {
|
||||
lock sync.RWMutex
|
||||
pool container.Set[string]
|
||||
}
|
||||
|
||||
// NewStatusTable initializes and returns a new StatusTable object.
|
||||
func NewStatusTable() *StatusTable {
|
||||
return &StatusTable{
|
||||
pool: make(container.Set[string]),
|
||||
}
|
||||
}
|
||||
|
||||
// StartIfNotRunning sets value of given name to true if not already in pool.
|
||||
// Returns whether set value was set to true
|
||||
func (p *StatusTable) StartIfNotRunning(name string) bool {
|
||||
p.lock.Lock()
|
||||
added := p.pool.Add(name)
|
||||
p.lock.Unlock()
|
||||
return added
|
||||
}
|
||||
|
||||
// Start sets value of given name to true in the pool.
|
||||
func (p *StatusTable) Start(name string) {
|
||||
p.lock.Lock()
|
||||
p.pool.Add(name)
|
||||
p.lock.Unlock()
|
||||
}
|
||||
|
||||
// Stop sets value of given name to false in the pool.
|
||||
func (p *StatusTable) Stop(name string) {
|
||||
p.lock.Lock()
|
||||
p.pool.Remove(name)
|
||||
p.lock.Unlock()
|
||||
}
|
||||
|
||||
// IsRunning checks if value of given name is set to true in the pool.
|
||||
func (p *StatusTable) IsRunning(name string) bool {
|
||||
p.lock.RLock()
|
||||
exists := p.pool.Contains(name)
|
||||
p.lock.RUnlock()
|
||||
return exists
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sync
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_StatusTable(t *testing.T) {
|
||||
table := NewStatusTable()
|
||||
|
||||
assert.False(t, table.IsRunning("xyz"))
|
||||
|
||||
table.Start("xyz")
|
||||
assert.True(t, table.IsRunning("xyz"))
|
||||
|
||||
assert.False(t, table.StartIfNotRunning("xyz"))
|
||||
assert.True(t, table.IsRunning("xyz"))
|
||||
|
||||
table.Stop("xyz")
|
||||
assert.False(t, table.IsRunning("xyz"))
|
||||
|
||||
assert.True(t, table.StartIfNotRunning("xyz"))
|
||||
assert.True(t, table.IsRunning("xyz"))
|
||||
|
||||
table.Stop("xyz")
|
||||
assert.False(t, table.IsRunning("xyz"))
|
||||
}
|
@ -551,6 +551,13 @@ repo.transfer.body=承認または拒否するには %s を開きます。 も
|
||||
repo.collaborator.added.subject=%s が %s にあなたを追加しました
|
||||
repo.collaborator.added.text=あなたは次のリポジトリの共同作業者に追加されました:
|
||||
|
||||
repo.actions.run.failed=実行に失敗しました
|
||||
repo.actions.run.succeeded=実行に成功しました
|
||||
repo.actions.run.cancelled=実行がキャンセルされました
|
||||
repo.actions.jobs.all_succeeded=すべてのジョブが成功しました
|
||||
repo.actions.jobs.all_failed=すべてのジョブが失敗しました
|
||||
repo.actions.jobs.some_not_successful=いくつかのジョブが成功しませんでした
|
||||
repo.actions.jobs.all_cancelled=すべてのジョブがキャンセルされました
|
||||
|
||||
team_invite.subject=%[1]s さんが %[2]s への参加にあなたを招待しました
|
||||
team_invite.text_1=%[1]s さんが、組織 %[3]s 内のチーム %[2]s への参加に、あなたを招待しました。
|
||||
@ -3722,10 +3729,14 @@ swift.install=あなたの <code>Package.swift</code> ファイルにパッケ
|
||||
swift.install2=そして次のコマンドを実行します:
|
||||
vagrant.install=Vagrant ボックスを追加するには、次のコマンドを実行します。
|
||||
settings.link=このパッケージをリポジトリにリンク
|
||||
settings.link.description=パッケージをリポジトリにリンクすると、リポジトリのパッケージ一覧にそのパッケージが表示されます。 リンクできるのは、同じオーナーが所有するリポジトリのみです。 入力欄を空にするとリンクを削除します。
|
||||
settings.link.select=リポジトリを選択
|
||||
settings.link.button=リポジトリのリンクを更新
|
||||
settings.link.success=リポジトリのリンクが正常に更新されました。
|
||||
settings.link.success=リポジトリのリンクを更新しました。
|
||||
settings.link.error=リポジトリのリンクの更新に失敗しました。
|
||||
settings.link.repo_not_found=リポジトリ %s が見つかりません。
|
||||
settings.unlink.error=リポジトリのリンクの削除に失敗しました。
|
||||
settings.unlink.success=リポジトリのリンクを削除しました。
|
||||
settings.delete=パッケージ削除
|
||||
settings.delete.description=パッケージの削除は恒久的で元に戻すことはできません。
|
||||
settings.delete.notice=%s (%s) を削除しようとしています。この操作は元に戻せません。よろしいですか?
|
||||
|
@ -1052,6 +1052,8 @@ license=Licença
|
||||
license_helper=Selecione um arquivo de licença.
|
||||
license_helper_desc=Uma licença define o que os outros podem e não podem fazer com o seu código. Não tem certeza qual é a mais adequada para o seu projeto? Veja <a target="_blank" rel="noopener noreferrer" href="%s">Escolher uma licença.</a>
|
||||
multiple_licenses=Múltiplas Licenças
|
||||
object_format=Formato de Objeto
|
||||
object_format_helper=Formato do objeto do repositório. Não pode ser alterado mais tarde. SHA1 é mais compatível.
|
||||
readme=README
|
||||
readme_helper=Selecione um modelo de arquivo README.
|
||||
readme_helper_desc=Aqui você pode escrever uma descrição completa para o seu projeto.
|
||||
@ -1101,6 +1103,8 @@ delete_preexisting=Excluir arquivos pré-existentes
|
||||
delete_preexisting_content=Excluir arquivos em %s
|
||||
delete_preexisting_success=Arquivos órfãos excluídos em %s
|
||||
blame_prior=Ver a responsabilização anterior a esta modificação
|
||||
blame.ignore_revs=Ignorando revisões presentes em <a href="%s">.git-blame-ignore-revs</a>. Clique <a href="%s">aqui para ignorar</a> e ver o blame normalmente.
|
||||
blame.ignore_revs.failed=Falha ao ignorar revisões em <a href="%s">.git-blame-ignore-revs</a>.
|
||||
|
||||
|
||||
transfer.accept=Aceitar Transferência
|
||||
@ -1264,6 +1268,7 @@ ambiguous_runes_header=`Este arquivo contém caracteres Unicode ambíguos`
|
||||
ambiguous_runes_description=`Este arquivo contém caracteres Unicode que podem ser confundidos com outros caracteres. Se você acha que isso é intencional, pode ignorar esse aviso com segurança. Use o botão Escapar para revelá-los`
|
||||
invisible_runes_line=`Esta linha tem caracteres unicode invisíveis`
|
||||
ambiguous_runes_line=`Esta linha tem caracteres unicode ambíguos`
|
||||
ambiguous_character=`%[1]c [U+%04[1]X] pode ser confundido com %[2]c [U+%04[2]X]`
|
||||
|
||||
escape_control_characters=Escapar
|
||||
unescape_control_characters=Desescapar
|
||||
@ -1273,6 +1278,7 @@ video_not_supported_in_browser=Seu navegador não suporta a tag 'video' do HTML5
|
||||
audio_not_supported_in_browser=Seu navegador não suporta a tag 'audio' do HTML5.
|
||||
symbolic_link=Link simbólico
|
||||
executable_file=Arquivo Executável
|
||||
vendored=Externo
|
||||
generated=Gerado
|
||||
commit_graph=Gráfico de Commits
|
||||
commit_graph.select=Selecionar branches
|
||||
@ -1392,6 +1398,7 @@ commitstatus.success=Sucesso
|
||||
ext_issues=Acesso a Issues Externos
|
||||
ext_issues.desc=Link para o issue tracker externo.
|
||||
|
||||
projects.desc=Gerencie issues e PRs nos quadros do projeto.
|
||||
projects.description=Descrição (opcional)
|
||||
projects.description_placeholder=Descrição
|
||||
projects.create=Criar Projeto
|
||||
@ -1419,6 +1426,7 @@ projects.column.new=Nova Coluna
|
||||
projects.column.set_default=Atribuir como Padrão
|
||||
projects.column.set_default_desc=Definir esta coluna como padrão para pull e issues sem categoria
|
||||
projects.column.delete=Excluir Coluna
|
||||
projects.column.deletion_desc=Excluir uma coluna do projeto move todas as issues relacionadas para a coluna padrão. Continuar?
|
||||
projects.column.color=Cor
|
||||
projects.open=Abrir
|
||||
projects.close=Fechar
|
||||
@ -1452,6 +1460,7 @@ issues.new.clear_milestone=Limpar marco
|
||||
issues.new.assignees=Responsáveis
|
||||
issues.new.clear_assignees=Limpar responsáveis
|
||||
issues.new.no_assignees=Sem Responsáveis
|
||||
issues.new.no_reviewers=Sem Revisores
|
||||
issues.choose.get_started=Primeiros Passos
|
||||
issues.choose.open_external_link=Abrir
|
||||
issues.choose.blank=Padrão
|
||||
@ -1516,6 +1525,7 @@ issues.filter_type.reviewed_by_you=Revisado por você
|
||||
issues.filter_sort=Ordenação
|
||||
issues.filter_sort.latest=Mais recentes
|
||||
issues.filter_sort.oldest=Mais antigos
|
||||
issues.filter_sort.recentupdate=Mais recentemente atualizados
|
||||
issues.filter_sort.leastupdate=Menos recentemente atualizados
|
||||
issues.filter_sort.mostcomment=Mais comentados
|
||||
issues.filter_sort.leastcomment=Menos comentados
|
||||
@ -1635,6 +1645,7 @@ issues.lock_no_reason=bloqueada e conversação limitada para colaboradores %s
|
||||
issues.unlock_comment=desbloqueada esta conversação %s
|
||||
issues.lock_confirm=Bloquear
|
||||
issues.unlock_confirm=Desbloquear
|
||||
issues.lock.notice_1=- Outros usuários não podem adicionar novos comentários a esta issue.
|
||||
issues.lock.notice_2=- Você e outros colaboradores com acesso a este repositório ainda podem deixar comentários que outros podem ver.
|
||||
issues.lock.notice_3=- Você pode sempre desbloquear esta issue novamente no futuro.
|
||||
issues.unlock.notice_1=- Todos poderão comentar mais uma vez nesta issue.
|
||||
@ -1660,6 +1671,7 @@ issues.stop_tracking=Parar Cronômetro
|
||||
issues.stop_tracking_history=trabalhou por <b>%[1]s</b> %[2]s
|
||||
issues.cancel_tracking_history=`cronômetro cancelado %s`
|
||||
issues.del_time=Apagar este registro de tempo
|
||||
issues.add_time_history=adicionou tempo gasto <b>%[1]s</b> %[2]s
|
||||
issues.del_time_history=`removeu tempo gasto %s`
|
||||
issues.add_time_hours=Horas
|
||||
issues.add_time_minutes=Minutos
|
||||
@ -1679,6 +1691,7 @@ issues.due_date_form=dd/mm/aaaa
|
||||
issues.due_date_form_add=Adicionar data limite
|
||||
issues.due_date_form_edit=Editar
|
||||
issues.due_date_form_remove=Remover
|
||||
issues.due_date_not_writer=Você precisa de acesso de escrita neste repositório para atualizar a data limite de uma issue.
|
||||
issues.due_date_not_set=Data limite não definida.
|
||||
issues.due_date_added=adicionou a data limite %s %s
|
||||
issues.due_date_modified=modificou a data limite de %[2]s para %[1]s %[3]s
|
||||
@ -1701,7 +1714,9 @@ issues.dependency.pr_closing_blockedby=Fechamento deste pull request está bloqu
|
||||
issues.dependency.issue_closing_blockedby=Fechamento desta issue está bloqueado pelas seguintes issues
|
||||
issues.dependency.issue_close_blocks=Esta issue bloqueia o fechamento das seguintes issues
|
||||
issues.dependency.pr_close_blocks=Este pull request bloqueia o fechamento das seguintes issues
|
||||
issues.dependency.issue_close_blocked=Você precisa fechar todas as issues que bloqueiam esta issue antes de poder fechá-la.
|
||||
issues.dependency.issue_batch_close_blocked=Não é possível fechar as issues que você escolheu, porque a issue #%d ainda tem dependências abertas
|
||||
issues.dependency.pr_close_blocked=Você precisa fechar todas issues que bloqueiam este pull request antes de poder fazer o merge.
|
||||
issues.dependency.blocks_short=Bloqueia
|
||||
issues.dependency.blocked_by_short=Depende de
|
||||
issues.dependency.remove_header=Remover Dependência
|
||||
@ -1712,11 +1727,13 @@ issues.dependency.add_error_same_issue=Você não pode fazer uma issue depender
|
||||
issues.dependency.add_error_dep_issue_not_exist=Issue dependente não existe.
|
||||
issues.dependency.add_error_dep_not_exist=Dependência não existe.
|
||||
issues.dependency.add_error_dep_exists=Dependência já existe.
|
||||
issues.dependency.add_error_cannot_create_circular=Você não pode criar uma dependência com duas issues que se bloqueiam mutuamente.
|
||||
issues.dependency.add_error_dep_not_same_repo=Ambas as issues devem estar no mesmo repositório.
|
||||
issues.review.self.approval=Você não pode aprovar o seu próprio pull request.
|
||||
issues.review.self.rejection=Você não pode solicitar alterações em seu próprio pull request.
|
||||
issues.review.approve=aprovou estas alterações %s
|
||||
issues.review.comment=revisou %s
|
||||
issues.review.dismissed=dispensou a revisão de %s %s
|
||||
issues.review.dismissed_label=Rejeitada
|
||||
issues.review.left_comment=deixou um comentário
|
||||
issues.review.content.empty=Você precisa deixar um comentário indicando as alterações solicitadas.
|
||||
@ -1776,9 +1793,11 @@ pulls.show_all_commits=Mostrar todos os commits
|
||||
pulls.show_changes_since_your_last_review=Mostrar alterações desde sua última revisão
|
||||
pulls.showing_only_single_commit=Mostrando apenas as alterações do commit %[1]s
|
||||
pulls.showing_specified_commit_range=Mostrando apenas as alterações entre %[1]s..%[2]s
|
||||
pulls.select_commit_hold_shift_for_range=Selecionar commit. Mantenha pressionado shift + clique para selecionar um intervalo
|
||||
pulls.review_only_possible_for_full_diff=A revisão só é possível ao visualizar todas as diferenças
|
||||
pulls.filter_changes_by_commit=Filtrar por commit
|
||||
pulls.nothing_to_compare=Estes branches são iguais. Não há nenhuma necessidade para criar um pull request.
|
||||
pulls.nothing_to_compare_have_tag=Os branches/tags selecionados são iguais.
|
||||
pulls.nothing_to_compare_and_allow_empty_pr=Estes branches são iguais. Este PR ficará vazio.
|
||||
pulls.has_pull_request=`Um pull request entre esses branches já existe: <a href="%[1]s">%[2]s#%[3]d</a>`
|
||||
pulls.create=Criar Pull Request
|
||||
@ -1803,11 +1822,13 @@ pulls.add_prefix=Adicione o prefixo <strong>%s</strong>
|
||||
pulls.remove_prefix=Remover o prefixo <strong>%s</strong>
|
||||
pulls.data_broken=Este pull request está quebrado devido a falta de informação do fork.
|
||||
pulls.files_conflicted=Este pull request tem alterações conflitantes com o branch de destino.
|
||||
pulls.is_checking=Verificando conflitos de merge…
|
||||
pulls.is_ancestor=Este branch já está incluído no branch de destino. Não há nada para mesclar.
|
||||
pulls.is_empty=As alterações neste branch já estão na branch de destino. Este será um commit vazio.
|
||||
pulls.required_status_check_failed=Algumas verificações necessárias não foram bem sucedidas.
|
||||
pulls.required_status_check_missing=Estão faltando algumas verificações necessárias.
|
||||
pulls.required_status_check_administrator=Como administrador, você ainda pode aplicar o merge deste pull request.
|
||||
pulls.blocked_by_approvals=Este pull request ainda não tem aprovações suficientes. %d de %d aprovações concedidas.
|
||||
pulls.blocked_by_rejection=Este pull request tem alterações solicitadas por um revisor oficial.
|
||||
pulls.blocked_by_official_review_requests=Este pull request tem solicitações de revisão oficiais.
|
||||
pulls.blocked_by_outdated_branch=Este pull request está bloqueado porque está desatualizado.
|
||||
@ -1824,12 +1845,16 @@ pulls.reject_count_1=%d pedido de alteração
|
||||
pulls.reject_count_n=%d pedidos de alteração
|
||||
pulls.waiting_count_1=aguardando %d revisão
|
||||
pulls.waiting_count_n=aguardando %d revisões
|
||||
pulls.wrong_commit_id=id de commit tem que ser um id de commit no branch de destino
|
||||
|
||||
pulls.no_merge_desc=O merge deste pull request não pode ser aplicado porque todas as opções de mesclagem do repositório estão desabilitadas.
|
||||
pulls.no_merge_helper=Habilite as opções de merge nas configurações do repositório ou faça o merge do pull request manualmente.
|
||||
pulls.no_merge_wip=O merge deste pull request não pode ser aplicado porque está marcado como um trabalho em andamento.
|
||||
pulls.no_merge_not_ready=Este pull request não está pronto para o merge, verifique o status da revisão e as verificações de status.
|
||||
pulls.no_merge_access=Você não está autorizado para realizar o merge deste pull request.
|
||||
pulls.merge_pull_request=Criar commit de merge
|
||||
pulls.rebase_merge_pull_request=Rebase e fast-forward
|
||||
pulls.rebase_merge_commit_pull_request=Rebase e criar commit de merge
|
||||
pulls.squash_merge_pull_request=Criar commit de squash
|
||||
pulls.fast_forward_only_merge_pull_request=Apenas Fast-forward
|
||||
pulls.merge_manually=Merge feito manualmente
|
||||
@ -1837,9 +1862,17 @@ pulls.merge_commit_id=A ID de merge commit
|
||||
pulls.require_signed_wont_sign=O branch requer commits assinados, mas este merge não será assinado
|
||||
|
||||
pulls.invalid_merge_option=Você não pode usar esta opção de merge neste pull request.
|
||||
pulls.merge_conflict=Merge Falhou: houve um conflito ao fazer merge. Dica: Tente uma estratégia diferente.
|
||||
pulls.merge_conflict_summary=Mensagem de Erro
|
||||
pulls.rebase_conflict=Merge Falhou: houve um conflito durante o rebase do commit %[1]s. Dica: Tente uma estratégia diferente.
|
||||
pulls.rebase_conflict_summary=Mensagem de Erro
|
||||
pulls.unrelated_histories=Merge Falhou: O merge do principal e da base não compartilham uma história comum. Dica: Tente uma estratégia diferente.
|
||||
pulls.merge_out_of_date=Merge Falhou: durante a geração do merge, a base não foi atualizada. Dica: Tente novamente.
|
||||
pulls.head_out_of_date=Merge Falhou: qnquanto gerava o merge, a head foi atualizada. Dica: Tente novamente.
|
||||
pulls.has_merged=Falha: o merge do pull request foi realizado, você não pode fazer o merge novamente ou alterar o branch de destino.
|
||||
pulls.push_rejected=Merge Falhou: O push foi rejeitado. Revise os Git Hooks para este repositório.
|
||||
pulls.push_rejected_summary=Mensagem Completa da Rejeição
|
||||
pulls.push_rejected_no_message=Merge Falhou: O push foi rejeitado mas não houve mensagem remota. Revise os Git Hooks para este repositório.
|
||||
pulls.open_unmerged_pull_exists=`Não é possível executar uma operação de reabertura pois há um pull request pendente (#%d) com propriedades idênticas.`
|
||||
pulls.status_checking=Algumas verificações estão pendentes
|
||||
pulls.status_checks_success=Todas as verificações foram bem sucedidas
|
||||
@ -1858,10 +1891,13 @@ pulls.outdated_with_base_branch=Este branch está desatualizado com o branch bas
|
||||
pulls.close=Fechar Pull Request
|
||||
pulls.closed_at=`fechou este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
pulls.reopened_at=`reabriu este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
pulls.cmd_instruction_hint=Ver instruções de linha de comando
|
||||
pulls.cmd_instruction_checkout_title=Checkout
|
||||
pulls.cmd_instruction_checkout_desc=No repositório do seu projeto, faça checkout de um novo branch e teste as alterações.
|
||||
pulls.cmd_instruction_merge_title=Merge
|
||||
pulls.cmd_instruction_merge_desc=Faça merge das alterações e atualize no Gitea.
|
||||
pulls.clear_merge_message=Limpar mensagem do merge
|
||||
pulls.clear_merge_message_hint=Limpar a mensagem de merge só irá remover o conteúdo da mensagem de commit e manter trailers git gerados, como "Co-Authored-By …".
|
||||
|
||||
pulls.auto_merge_button_when_succeed=(Quando a verificação for bem-sucedida)
|
||||
pulls.auto_merge_when_succeed=Mesclar automaticamente quando todas as verificações forem bem sucedidas
|
||||
@ -1917,12 +1953,15 @@ milestones.filter_sort.most_issues=Com mais issues
|
||||
milestones.filter_sort.least_issues=Com menos issues
|
||||
|
||||
signing.will_sign=Esse commit será assinado com a chave "%s".
|
||||
signing.wont_sign.error=Ocorreu um erro ao verificar se o commit poderia ser assinado.
|
||||
signing.wont_sign.nokey=Não há nenhuma chave disponível para assinar esse commit.
|
||||
signing.wont_sign.never=Commits nunca são assinados.
|
||||
signing.wont_sign.always=Commits são sempre assinados.
|
||||
signing.wont_sign.pubkey=O commit não será assinado porque você não tem uma chave pública associada à sua conta.
|
||||
signing.wont_sign.twofa=Você deve ter a autenticação de dois fatores ativada para que os commits sejam assinados.
|
||||
signing.wont_sign.parentsigned=O commit não será assinado, pois o commit pai não está assinado.
|
||||
signing.wont_sign.basesigned=O merge não será assinado, pois o commit base não está assinado.
|
||||
signing.wont_sign.headsigned=O merge não será assinado, pois o commit mais recente não está assinado.
|
||||
signing.wont_sign.commitssigned=O merge não será assinado, pois todos os commits associados não estão assinados.
|
||||
signing.wont_sign.approved=O merge não será assinado porque o PR não foi aprovado.
|
||||
signing.wont_sign.not_signed_in=Você não está conectado.
|
||||
@ -1959,6 +1998,7 @@ wiki.original_git_entry_tooltip=Ver o arquivo Git original em vez de usar o link
|
||||
|
||||
activity=Atividade
|
||||
activity.navbar.pulse=Pulso
|
||||
activity.navbar.code_frequency=Frequência de Código
|
||||
activity.navbar.contributors=Contribuidores
|
||||
activity.navbar.recent_commits=Commits Recentes
|
||||
activity.period.filter_label=Período:
|
||||
@ -2004,6 +2044,7 @@ activity.title.releases_1=%d Versão
|
||||
activity.title.releases_n=%d Versões
|
||||
activity.title.releases_published_by=%s publicada(s) por %s
|
||||
activity.published_release_label=Publicado
|
||||
activity.no_git_activity=Não houve nenhuma atividade de commit neste período.
|
||||
activity.git_stats_exclude_merges=Excluindo merges,
|
||||
activity.git_stats_author_1=%d autor
|
||||
activity.git_stats_author_n=%d autores
|
||||
@ -2031,6 +2072,7 @@ contributors.contribution_type.additions=Adições
|
||||
contributors.contribution_type.deletions=Exclusões
|
||||
|
||||
settings=Configurações
|
||||
settings.desc=Configurações é onde você pode gerenciar as opções para o repositório.
|
||||
settings.options=Repositório
|
||||
settings.public_access=Acesso Público
|
||||
settings.collaboration=Colaboradores
|
||||
@ -2044,10 +2086,19 @@ settings.githooks=Hooks do Git
|
||||
settings.basic_settings=Configurações Básicas
|
||||
settings.mirror_settings=Opções de Espelhamento
|
||||
settings.mirror_settings.docs=Configure o seu repositório para sincronizar automaticamente commits, tags e branches de outro repositório.
|
||||
settings.mirror_settings.docs.disabled_pull_mirror.instructions=Configure seu projeto para enviar automaticamente commits, tags e branches para outro repositório. Os espelhamentos pull foram desativados pelo administrador do site.
|
||||
settings.mirror_settings.docs.disabled_push_mirror.instructions=Configure seu projeto para puxar automaticamente commits, tags e branches de outro repositório.
|
||||
settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=No momento, isso só pode ser feito no menu "Nova migração". Para obter mais informações, consulte:
|
||||
settings.mirror_settings.docs.disabled_push_mirror.info=Os espelhamentos push foram desativados pelo administrador do site.
|
||||
settings.mirror_settings.docs.no_new_mirrors=Seu repositório está espelhando alterações de ou para outro repositório. Lembre-se de que, no momento, não é possível criar novos espelhamentos.
|
||||
settings.mirror_settings.docs.can_still_use=Embora não seja possível modificar os espelhos existentes ou criar novos espelhos, você ainda pode usar o espelho existente.
|
||||
settings.mirror_settings.docs.pull_mirror_instructions=Para configurar um espelhamento de pull, consulte:
|
||||
settings.mirror_settings.docs.more_information_if_disabled=Você pode saber mais sobre espelhos de push e pull aqui:
|
||||
settings.mirror_settings.docs.doc_link_title=Como posso espelhar repositórios?
|
||||
settings.mirror_settings.docs.doc_link_pull_section=a seção "Extração de um repositório remoto" da documentação.
|
||||
settings.mirror_settings.docs.pulling_remote_title=Extração de um repositório remoto
|
||||
settings.mirror_settings.mirrored_repository=Repositório espelhado
|
||||
settings.mirror_settings.pushed_repository=Repositório enviado
|
||||
settings.mirror_settings.direction=Sentido
|
||||
settings.mirror_settings.direction.pull=Pull
|
||||
settings.mirror_settings.direction.push=Push
|
||||
@ -2058,6 +2109,8 @@ settings.mirror_settings.push_mirror.add=Adicionar Espelho de Push
|
||||
settings.mirror_settings.push_mirror.edit_sync_time=Editar intervalo de sincronização de espelhos
|
||||
|
||||
settings.sync_mirror=Sincronizar Agora
|
||||
settings.pull_mirror_sync_in_progress=Puxando modificações a partir do repositório remoto %s, neste momento.
|
||||
settings.push_mirror_sync_in_progress=Enviando modificações para o repositório remoto %s, neste momento.
|
||||
settings.site=Site
|
||||
settings.update_settings=Atualizar Configurações
|
||||
settings.update_mirror_settings=Atualizar Configurações de Espelhamento
|
||||
@ -2073,24 +2126,24 @@ settings.external_wiki_url=URL Externa da Wiki
|
||||
settings.external_wiki_url_error=A URL da wiki externa não é válida.
|
||||
settings.external_wiki_url_desc=Visitantes são redirecionados para a URL da wiki externa ao clicar na aba da wiki.
|
||||
settings.issues_desc=Habilitar Issue Tracker para o Repositório
|
||||
settings.use_internal_issue_tracker=Usar o issue tracker nativo
|
||||
settings.use_external_issue_tracker=Usar issue tracker externo
|
||||
settings.use_internal_issue_tracker=Usar o Issue Tracker Nativo
|
||||
settings.use_external_issue_tracker=Usar Issue Tracker Externo
|
||||
settings.external_tracker_url=URL do Issue Tracker Externo
|
||||
settings.external_tracker_url_error=A URL do issue tracker externo não é válida.
|
||||
settings.external_tracker_url_desc=Visitantes são redirecionados para a URL do issue tracker externo ao clicar na aba de issues.
|
||||
settings.tracker_url_format=Formato de URL do issue tracker externo
|
||||
settings.tracker_url_format_error=O formato da URL do issue tracker externo não é válido.
|
||||
settings.tracker_issue_style=Formato de número do issue tracker externo
|
||||
settings.tracker_issue_style=Formato de Número do Issue Tracker Externo
|
||||
settings.tracker_issue_style.numeric=Numérico
|
||||
settings.tracker_issue_style.alphanumeric=Alfanumérico
|
||||
settings.tracker_issue_style.regexp=Expressão Regular
|
||||
settings.tracker_issue_style.regexp_pattern=Padrão de expressão regular
|
||||
settings.tracker_issue_style.regexp_pattern=Padrão de Expressão Regular
|
||||
settings.tracker_issue_style.regexp_pattern_desc=O primeiro grupo capturado será usado no lugar de <code>{index}</code>.
|
||||
settings.tracker_url_format_desc=Use os espaços reservados <code>{user}</code>, <code>{repo}</code> e <code>{index}</code> para o nome de usuário, nome do repositório e o índice de problemas.
|
||||
settings.enable_timetracker=Habilitar Cronômetro
|
||||
settings.allow_only_contributors_to_track_time=Permitir que apenas os colaboradores acompanhem o contador de tempo
|
||||
settings.pulls_desc=Habilitar pull requests no repositório
|
||||
settings.pulls.ignore_whitespace=Ignorar espaço em branco em conflitos
|
||||
settings.pulls_desc=Habilitar Pull Requests no Repositório
|
||||
settings.pulls.ignore_whitespace=Ignorar Espaço em Branco em Conflitos
|
||||
settings.pulls.enable_autodetect_manual_merge=Habilitar a detecção automática de merge manual (Nota: Em alguns casos especiais, podem ocorrer julgamentos errados)
|
||||
settings.pulls.allow_rebase_update=Ativar atualização do branch do pull request por rebase
|
||||
settings.pulls.default_delete_branch_after_merge=Excluir o branch de pull request após o merge por padrão
|
||||
@ -2098,6 +2151,8 @@ settings.pulls.default_allow_edits_from_maintainers=Permitir edições de manten
|
||||
settings.releases_desc=Habilitar versões do Repositório
|
||||
settings.packages_desc=Habilitar Registro de Pacotes de Repositório
|
||||
settings.projects_desc=Habilitar Projetos
|
||||
settings.projects_mode_repo=Somente projetos no repositório
|
||||
settings.projects_mode_owner=Apenas projetos do usuário ou org
|
||||
settings.projects_mode_all=Todos os projetos
|
||||
settings.actions_desc=Habilitar Ações do Repositório
|
||||
settings.admin_settings=Configurações do Administrador
|
||||
@ -2111,7 +2166,7 @@ settings.reindex_requested=Reindexação Requisitada
|
||||
settings.admin_enable_close_issues_via_commit_in_any_branch=Fechar issue via commit em um branch não padrão
|
||||
settings.danger_zone=Zona de Perigo
|
||||
settings.new_owner_has_same_repo=O novo proprietário já tem um repositório com o mesmo nome. Por favor, escolha outro nome.
|
||||
settings.convert=Converter para repositório tradicional
|
||||
settings.convert=Converter para Repositório Tradicional
|
||||
settings.convert_desc=Você pode converter este espelhamento em um repositório tradicional. Esta ação não pode ser revertida.
|
||||
settings.convert_notices_1=Esta operação vai converter este espelhamento em um repositório tradicional. Esta ação não pode ser desfeita.
|
||||
settings.convert_confirm=Converter o Repositório
|
||||
@ -2129,6 +2184,7 @@ settings.transfer_abort_invalid=Não é possível cancelar uma transferência de
|
||||
settings.transfer_abort_success=A transferência do repositório para %s foi cancelada com sucesso.
|
||||
settings.transfer_desc=Transferir este repositório para outro usuário ou para uma organização onde você tem direitos de administrador.
|
||||
settings.transfer_form_title=Digite o nome do repositório para confirmar:
|
||||
settings.transfer_in_progress=Há uma transferência em andamento. Por favor, cancele se você gostaria de transferir este repositório para outro usuário.
|
||||
settings.transfer_notices_1=- Você perderá o acesso ao repositório se transferir para um usuário individual.
|
||||
settings.transfer_notices_2=- Você manterá acesso ao repositório se transferi-lo para uma organização que você também é proprietário.
|
||||
settings.transfer_notices_3=- Se o repositório for privado e for transferido para um usuário individual, esta ação certifica que o usuário tem pelo menos permissão de leitura (e altera as permissões se necessário).
|
||||
@ -2142,9 +2198,13 @@ settings.trust_model.default=Modelo Padrão de Confiança
|
||||
settings.trust_model.default.desc=Use o modelo de confiança de repositório padrão para esta instalação.
|
||||
settings.trust_model.collaborator=Colaborador
|
||||
settings.trust_model.collaborator.long=Colaborador: Confiar em assinaturas feitas por colaboradores
|
||||
settings.trust_model.collaborator.desc=Assinaturas válidas dos colaboradores deste repositório serão marcadas como "confiáveis" - (quer correspondam ao autor do commit ou não). Caso contrário, assinaturas válidas serão marcadas como "não confiáveis" se a assinatura corresponder ao autor do commit e "não corresponde" se não corresponder.
|
||||
settings.trust_model.committer=Committer
|
||||
settings.trust_model.committer.long=Committer: Confiar nas assinaturas que correspondam aos autores de commits (isso corresponde ao GitHub e forçará commits assinados pelo Gitea a ter o Gitea como o autor do commit).
|
||||
settings.trust_model.committer.desc=As assinaturas válidas só serão marcadas como "confiáveis" se corresponderem ao autor do commit; caso contrário, serão marcadas como "não correspondentes". Isso força o Gitea a ser o autor do commit em commits assinados com o autor do commit real marcado como Co-authored-by: e Co-committed-by: no rodapé do commit. A chave padrão do Gitea deve corresponder a um usuário no banco de dados.
|
||||
settings.trust_model.collaboratorcommitter=Colaborador+Commiter
|
||||
settings.trust_model.collaboratorcommitter.long=Colaborador+Committer: Confiar na assinatura dos colaboradores que correspondem ao autor do commit
|
||||
settings.trust_model.collaboratorcommitter.desc=Assinaturas válidas dos colaboradores deste repositório serão marcadas como "confiáveis" se corresponderem ao autor do commit. Caso contrário, as assinaturas válidas serão marcadas como "não confiáveis" se a assinatura corresponder ao autor do commit e "não corresponde" caso contrário. Isso forçará o Gitea a ser marcado como o autor do commit nos commits assinados com o autor marcado como Co-Authored-By: e o Committed-By: resumo do commit. A chave padrão do Gitea tem que corresponder a um usuário no banco de dados.
|
||||
settings.wiki_delete=Excluir Dados da Wiki
|
||||
settings.wiki_delete_desc=A exclusão de dados da wiki é permanente e não pode ser desfeita.
|
||||
settings.wiki_delete_notices_1=- Isso excluirá e desabilitará permanentemente a wiki do repositório %s.
|
||||
@ -2153,9 +2213,11 @@ settings.wiki_deletion_success=Os dados da wiki do repositório foi excluídos.
|
||||
settings.delete=Excluir Este Repositório
|
||||
settings.delete_desc=A exclusão de um repositório é permanente e não pode ser desfeita.
|
||||
settings.delete_notices_1=- Esta operação <strong>NÃO PODERÁ</strong> ser desfeita.
|
||||
settings.delete_notices_2=- Essa operação excluirá permanentemente o repositório <strong>%s</strong>, incluindo código, issues, comentários, dados da wiki e configurações do colaborador.
|
||||
settings.delete_notices_fork_1=- Forks deste repositório se tornarão independentes após a exclusão.
|
||||
settings.deletion_success=O repositório foi excluído.
|
||||
settings.update_settings_success=As configurações do repositório foram atualizadas.
|
||||
settings.update_settings_no_unit=O repositório deve permitir pelo menos algum tipo de interação.
|
||||
settings.confirm_delete=Excluir Repositório
|
||||
settings.add_collaborator=Adicionar Colaborador
|
||||
settings.add_collaborator_success=O colaborador foi adicionado.
|
||||
@ -2172,6 +2234,8 @@ settings.team_not_in_organization=A equipe não está na mesma organização que
|
||||
settings.teams=Equipes
|
||||
settings.add_team=Adicionar Equipe
|
||||
settings.add_team_duplicate=A equipe já tem o repositório
|
||||
settings.add_team_success=A equipe agora tem acesso ao repositório.
|
||||
settings.change_team_permission_tip=A permissão da equipe está definida na página de configurações da equipe e não pode ser alterada por repositório
|
||||
settings.delete_team_tip=Esta equipe tem acesso a todos os repositórios e não pode ser removida
|
||||
settings.remove_team_success=O acesso da equipe ao repositório foi removido.
|
||||
settings.add_webhook=Adicionar Webhook
|
||||
@ -2181,12 +2245,15 @@ settings.webhook_deletion=Remover Webhook
|
||||
settings.webhook_deletion_desc=A exclusão de um webhook exclui suas configurações e o histórico de entrega. Continuar?
|
||||
settings.webhook_deletion_success=O webhook foi removido.
|
||||
settings.webhook.test_delivery=Testar Evento de Push
|
||||
settings.webhook.test_delivery_desc=Teste este webhook com um evento falso.
|
||||
settings.webhook.test_delivery_desc_disabled=Para testar este webhook com um evento falso, ative-o.
|
||||
settings.webhook.request=Solicitação
|
||||
settings.webhook.response=Resposta
|
||||
settings.webhook.headers=Cabeçalhos
|
||||
settings.webhook.payload=Conteúdo
|
||||
settings.webhook.body=Corpo
|
||||
settings.webhook.replay.description=Executar novamente esse webhook.
|
||||
settings.webhook.replay.description_disabled=Para repetir este webhook, ative-o.
|
||||
settings.webhook.delivery.success=Um evento foi adicionado à fila de envio. Pode levar alguns segundos até que ele apareça no histórico de envio.
|
||||
settings.githooks_desc=Hooks do Git são executados pelo próprio Git. Você pode editar arquivos de hook abaixo para configurar operações personalizadas.
|
||||
settings.githook_edit_desc=Se o hook não estiver ativo, o conteúdo de exemplo será apresentado. Deixar o conteúdo em branco irá desabilitar esse hook.
|
||||
@ -2249,6 +2316,8 @@ settings.event_pull_request_review=Pull Request Revisado
|
||||
settings.event_pull_request_review_desc=Pull request aprovado, rejeitado ou revisão comentada.
|
||||
settings.event_pull_request_sync=Pull Request Sincronizado
|
||||
settings.event_pull_request_sync_desc=Pull request sincronizado.
|
||||
settings.event_pull_request_review_request=Revisão de Pull Request Solicitada
|
||||
settings.event_pull_request_review_request_desc=Revisão de pull request solicitada ou solicitação de revisão removida.
|
||||
settings.event_package=Pacote
|
||||
settings.event_package_desc=Pacote criado ou excluído em um repositório.
|
||||
settings.branch_filter=Filtro de branch
|
||||
@ -2317,7 +2386,7 @@ settings.protect_check_status_contexts_list=Verificações de status encontradas
|
||||
settings.protect_required_approvals=Aprovações necessárias:
|
||||
settings.dismiss_stale_approvals=Descartar aprovações obsoletas
|
||||
settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo do pull request são enviados para o branch, as antigas aprovações serão descartadas.
|
||||
settings.require_signed_commits=Exigir commits assinados
|
||||
settings.require_signed_commits=Exigir Commits Assinados
|
||||
settings.require_signed_commits_desc=Rejeitar pushes para este branch se não estiverem assinados ou não forem validáveis.
|
||||
settings.protect_branch_name_pattern=Padrão de Nome de Branch Protegida
|
||||
settings.protect_patterns=Padrões
|
||||
@ -2328,6 +2397,7 @@ settings.delete_protected_branch=Desabilitar proteção
|
||||
settings.update_protect_branch_success=Proteção do branch "%s" foi atualizada.
|
||||
settings.remove_protected_branch_success=Proteção do branch "%s" foi desabilitada.
|
||||
settings.remove_protected_branch_failed=Removendo regra de proteção de branch "%s" falhou.
|
||||
settings.protected_branch_deletion=Excluir Proteção de Branch
|
||||
settings.protected_branch_deletion_desc=Desabilitar a proteção de branch permite que os usuários com permissão de escrita realizem push. Continuar?
|
||||
settings.block_rejected_reviews=Bloquear merge em revisões rejeitadas
|
||||
settings.block_rejected_reviews_desc=O merge não será possível quando são solicitadas alterações pelos revisores oficiais, mesmo que haja aprovação suficiente.
|
||||
@ -2359,6 +2429,7 @@ settings.matrix.room_id=ID da Sala
|
||||
settings.matrix.message_type=Tipo de Mensagem
|
||||
settings.visibility.private.button=Tornar Privado
|
||||
settings.visibility.public.button=Tornar Público
|
||||
settings.visibility.public.bullet_one=Deixe o repositório visível para qualquer pessoa.
|
||||
settings.archive.button=Arquivar Repositório
|
||||
settings.archive.header=Arquivar Este Repositório
|
||||
settings.archive.success=O repositório foi arquivado com sucesso.
|
||||
@ -2787,6 +2858,7 @@ users.activated=Ativado
|
||||
users.admin=Administrador
|
||||
users.restricted=Restrito
|
||||
users.reserved=Reservado
|
||||
users.bot=Bot
|
||||
users.remote=Remoto
|
||||
users.2fa=2FA
|
||||
users.repos=Repositórios
|
||||
@ -3536,6 +3608,7 @@ variables.update.success=A variável foi editada.
|
||||
type-1.display_name=Projeto Individual
|
||||
type-2.display_name=Projeto do Repositório
|
||||
type-3.display_name=Projeto da Organização
|
||||
exit_fullscreen=Sair da Tela Cheia
|
||||
|
||||
[git.filemode]
|
||||
changed_filemode=%[1]s → %[2]s
|
||||
|
@ -551,6 +551,13 @@ repo.transfer.body=访问 %s 以接受或拒绝转移,亦可忽略此邮件。
|
||||
repo.collaborator.added.subject=%s 把您添加到 %s
|
||||
repo.collaborator.added.text=您已被添加为仓库的协作者:
|
||||
|
||||
repo.actions.run.failed=运行失败
|
||||
repo.actions.run.succeeded=运行成功
|
||||
repo.actions.run.cancelled=运行已取消
|
||||
repo.actions.jobs.all_succeeded=所有任务已成功
|
||||
repo.actions.jobs.all_failed=所有任务已失败
|
||||
repo.actions.jobs.some_not_successful=一些任务未成功
|
||||
repo.actions.jobs.all_cancelled=所有任务已取消
|
||||
|
||||
team_invite.subject=%[1]s 邀请您加入组织 %[2]s
|
||||
team_invite.text_1=%[1]s 邀请您加入组织 %[3]s 中的团队 %[2]s。
|
||||
@ -3723,10 +3730,14 @@ swift.install=在您的 <code>Package.swift</code> 文件中添加该包:
|
||||
swift.install2=并运行以下命令:
|
||||
vagrant.install=若要添加一个 Vagrant box,请运行以下命令:
|
||||
settings.link=将此软件包链接到仓库
|
||||
settings.link.description=如果您将一个软件包与一个仓库链接起来,软件包将显示在仓库的软件包列表中。
|
||||
settings.link.select=选择仓库
|
||||
settings.link.button=更新仓库链接
|
||||
settings.link.success=仓库链接已成功更新。
|
||||
settings.link.error=更新仓库链接失败。
|
||||
settings.link.repo_not_found=仓库 %s 未找到。
|
||||
settings.unlink.error=删除仓库链接失败。
|
||||
settings.unlink.success=仓库链接已成功删除。
|
||||
settings.delete=删除软件包
|
||||
settings.delete.description=删除软件包是永久性的,无法撤消。
|
||||
settings.delete.notice=您将要删除 %s (%s)。此操作是不可逆的,您确定吗?
|
||||
|
@ -351,7 +351,7 @@ func ListIssues(ctx *context.APIContext) {
|
||||
// enum: [closed, open, all]
|
||||
// - name: labels
|
||||
// in: query
|
||||
// description: comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded
|
||||
// description: comma separated list of label names. Fetch only issues that have any of this label names. Non existent labels are discarded.
|
||||
// type: string
|
||||
// - name: q
|
||||
// in: query
|
||||
|
@ -1620,7 +1620,7 @@ func GetPullRequestFiles(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(baseGitRepo, startCommitID, endCommitID)
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, baseGitRepo, startCommitID, endCommitID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
@ -44,9 +44,11 @@ func isRoutePathExpensive(routePattern string) bool {
|
||||
"/{username}/{reponame}/blame/",
|
||||
"/{username}/{reponame}/commit/",
|
||||
"/{username}/{reponame}/commits/",
|
||||
"/{username}/{reponame}/compare/",
|
||||
"/{username}/{reponame}/graph",
|
||||
"/{username}/{reponame}/media/",
|
||||
"/{username}/{reponame}/raw/",
|
||||
"/{username}/{reponame}/rss/branch/",
|
||||
"/{username}/{reponame}/src/",
|
||||
|
||||
// issue & PR related (no trailing slash)
|
||||
|
@ -107,8 +107,8 @@ func ForwardedHeadersHandler(limit int, trustedProxies []string) func(h http.Han
|
||||
return proxy.ForwardedHeaders(opt)
|
||||
}
|
||||
|
||||
func Sessioner() func(next http.Handler) http.Handler {
|
||||
return session.Sessioner(session.Options{
|
||||
func Sessioner() (func(next http.Handler) http.Handler, error) {
|
||||
middleware, err := session.Sessioner(session.Options{
|
||||
Provider: setting.SessionConfig.Provider,
|
||||
ProviderConfig: setting.SessionConfig.ProviderConfig,
|
||||
CookieName: setting.SessionConfig.CookieName,
|
||||
@ -119,4 +119,9 @@ func Sessioner() func(next http.Handler) http.Handler {
|
||||
SameSite: setting.SessionConfig.SameSite,
|
||||
Domain: setting.SessionConfig.Domain,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create session middleware: %w", err)
|
||||
}
|
||||
|
||||
return middleware, nil
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ func InitWebInstalled(ctx context.Context) {
|
||||
mustInit(repo_service.InitLicenseClassifier)
|
||||
|
||||
// Finally start up the cron
|
||||
cron.NewContext(ctx)
|
||||
cron.Init(ctx)
|
||||
}
|
||||
|
||||
// NormalRoutes represents non install routes
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"html"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/public"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
@ -23,7 +24,11 @@ func Routes() *web.Router {
|
||||
base.Methods("GET, HEAD", "/assets/*", public.FileHandlerFunc())
|
||||
|
||||
r := web.NewRouter()
|
||||
r.Use(common.Sessioner(), Contexter())
|
||||
if sessionMid, err := common.Sessioner(); err == nil && sessionMid != nil {
|
||||
r.Use(sessionMid, Contexter())
|
||||
} else {
|
||||
log.Fatal("common.Sessioner failed: %v", err)
|
||||
}
|
||||
r.Get("/", Install) // it must be on the root, because the "install.js" use the window.location to replace the "localhost" AppURL
|
||||
r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall)
|
||||
r.Get("/post-install", InstallDone)
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/git"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
@ -56,7 +56,7 @@ func Activity(ctx *context.Context) {
|
||||
canReadCode := ctx.Repo.CanRead(unit.TypeCode)
|
||||
if canReadCode {
|
||||
// GetActivityStats needs to read the default branch to get some information
|
||||
branchExist, _ := git.IsBranchExist(ctx, ctx.Repo.Repository.ID, ctx.Repo.Repository.DefaultBranch)
|
||||
branchExist, _ := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, ctx.Repo.Repository.DefaultBranch)
|
||||
if !branchExist {
|
||||
ctx.Data["NotFoundPrompt"] = ctx.Tr("repo.branch.default_branch_not_exist", ctx.Repo.Repository.DefaultBranch)
|
||||
ctx.NotFound(nil)
|
||||
|
@ -324,7 +324,7 @@ func Diff(ctx *context.Context) {
|
||||
ctx.NotFound(err)
|
||||
return
|
||||
}
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(gitRepo, "", commitID)
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, gitRepo, "", commitID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDiffShortStat", err)
|
||||
return
|
||||
|
@ -631,7 +631,7 @@ func PrepareCompareDiff(
|
||||
ctx.ServerError("GetDiff", err)
|
||||
return false
|
||||
}
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ci.HeadGitRepo, beforeCommitID, headCommitID)
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ci.HeadRepo, ci.HeadGitRepo, beforeCommitID, headCommitID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDiffShortStat", err)
|
||||
return false
|
||||
|
@ -9,12 +9,14 @@ import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/renderhelper"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/htmlutil"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
@ -124,8 +126,8 @@ func NewComment(ctx *context.Context) {
|
||||
ctx.JSONError("The origin branch is delete, cannot reopen.")
|
||||
return
|
||||
}
|
||||
headBranchRef := pull.GetGitHeadBranchRefName()
|
||||
headBranchCommitID, err := git.GetFullCommitID(ctx, pull.HeadRepo.RepoPath(), headBranchRef)
|
||||
headBranchRef := git.RefNameFromBranch(pull.HeadBranch)
|
||||
headBranchCommitID, err := git.GetFullCommitID(ctx, pull.HeadRepo.RepoPath(), headBranchRef.String())
|
||||
if err != nil {
|
||||
ctx.ServerError("Get head commit Id of head branch fail", err)
|
||||
return
|
||||
@ -287,9 +289,10 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||
ctx.ServerError("RenderString", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
contentEmpty := fmt.Sprintf(`<span class="no-content">%s</span>`, ctx.Tr("repo.issues.no_content"))
|
||||
renderedContent = template.HTML(contentEmpty)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(string(renderedContent)) == "" {
|
||||
renderedContent = htmlutil.HTMLFormat(`<span class="no-content">%s</span>`, ctx.Tr("repo.issues.no_content"))
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
|
@ -201,7 +201,7 @@ func GetPullDiffStats(ctx *context.Context) {
|
||||
log.Error("Failed to GetRefCommitID: %v, repo: %v", err, ctx.Repo.Repository.FullName())
|
||||
return
|
||||
}
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, mergeBaseCommitID, headCommitID)
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, mergeBaseCommitID, headCommitID)
|
||||
if err != nil {
|
||||
log.Error("Failed to GetDiffShortStat: %v, repo: %v", err, ctx.Repo.Repository.FullName())
|
||||
return
|
||||
@ -761,7 +761,7 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
|
||||
}
|
||||
}
|
||||
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, beforeCommitID, afterCommitID)
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, beforeCommitID, afterCommitID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDiffShortStat", err)
|
||||
return
|
||||
|
@ -267,7 +267,11 @@ func Routes() *web.Router {
|
||||
routes.Get("/ssh_info", misc.SSHInfo)
|
||||
routes.Get("/api/healthz", healthcheck.Check)
|
||||
|
||||
mid = append(mid, common.Sessioner(), context.Contexter())
|
||||
if sessionMid, err := common.Sessioner(); err == nil && sessionMid != nil {
|
||||
mid = append(mid, sessionMid, context.Contexter())
|
||||
} else {
|
||||
log.Fatal("common.Sessioner failed: %v", err)
|
||||
}
|
||||
|
||||
// Get user from session if logged in.
|
||||
mid = append(mid, webAuth(buildAuthGroup()))
|
||||
|
@ -169,7 +169,7 @@ func (n *actionsNotifier) IssueChangeAssignee(ctx context.Context, doer *user_mo
|
||||
hookEvent = webhook_module.HookEventPullRequestAssign
|
||||
}
|
||||
|
||||
notifyIssueChange(ctx, doer, issue, hookEvent, action)
|
||||
notifyIssueChange(ctx, doer, issue, hookEvent, action, nil, nil)
|
||||
}
|
||||
|
||||
// IssueChangeMilestone notifies assignee to notifiers
|
||||
@ -188,11 +188,11 @@ func (n *actionsNotifier) IssueChangeMilestone(ctx context.Context, doer *user_m
|
||||
hookEvent = webhook_module.HookEventPullRequestMilestone
|
||||
}
|
||||
|
||||
notifyIssueChange(ctx, doer, issue, hookEvent, action)
|
||||
notifyIssueChange(ctx, doer, issue, hookEvent, action, nil, nil)
|
||||
}
|
||||
|
||||
func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue,
|
||||
_, _ []*issues_model.Label,
|
||||
addedLabels, removedLabels []*issues_model.Label,
|
||||
) {
|
||||
ctx = withMethod(ctx, "IssueChangeLabels")
|
||||
|
||||
@ -201,10 +201,10 @@ func (n *actionsNotifier) IssueChangeLabels(ctx context.Context, doer *user_mode
|
||||
hookEvent = webhook_module.HookEventPullRequestLabel
|
||||
}
|
||||
|
||||
notifyIssueChange(ctx, doer, issue, hookEvent, api.HookIssueLabelUpdated)
|
||||
notifyIssueChange(ctx, doer, issue, hookEvent, api.HookIssueLabelUpdated, addedLabels, removedLabels)
|
||||
}
|
||||
|
||||
func notifyIssueChange(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, event webhook_module.HookEventType, action api.HookIssueAction) {
|
||||
func notifyIssueChange(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, event webhook_module.HookEventType, action api.HookIssueAction, addedLabels, removedLabels []*issues_model.Label) {
|
||||
var err error
|
||||
if err = issue.LoadRepo(ctx); err != nil {
|
||||
log.Error("LoadRepo: %v", err)
|
||||
@ -216,34 +216,65 @@ func notifyIssueChange(ctx context.Context, doer *user_model.User, issue *issues
|
||||
return
|
||||
}
|
||||
|
||||
var addedAPILabels []*api.Label
|
||||
if addedLabels != nil {
|
||||
addedAPILabels = make([]*api.Label, 0, len(addedLabels))
|
||||
for _, label := range addedLabels {
|
||||
addedAPILabels = append(addedAPILabels, convert.ToLabel(label, issue.Repo, doer))
|
||||
}
|
||||
}
|
||||
|
||||
// Get removed labels from context if present
|
||||
var removedAPILabels []*api.Label
|
||||
if removedLabels != nil {
|
||||
removedAPILabels = make([]*api.Label, 0, len(removedLabels))
|
||||
for _, label := range removedLabels {
|
||||
removedAPILabels = append(removedAPILabels, convert.ToLabel(label, issue.Repo, doer))
|
||||
}
|
||||
}
|
||||
|
||||
if issue.IsPull {
|
||||
if err = issue.LoadPullRequest(ctx); err != nil {
|
||||
log.Error("loadPullRequest: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
payload := &api.PullRequestPayload{
|
||||
Action: action,
|
||||
Index: issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm_model.AccessModeNone}),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
Changes: &api.ChangesPayload{
|
||||
AddedLabels: addedAPILabels,
|
||||
RemovedLabels: removedAPILabels,
|
||||
},
|
||||
}
|
||||
|
||||
newNotifyInputFromIssue(issue, event).
|
||||
WithDoer(doer).
|
||||
WithPayload(&api.PullRequestPayload{
|
||||
Action: action,
|
||||
Index: issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm_model.AccessModeNone}),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
}).
|
||||
WithPayload(payload).
|
||||
WithPullRequest(issue.PullRequest).
|
||||
Notify(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, issue.Poster)
|
||||
payload := &api.IssuePayload{
|
||||
Action: action,
|
||||
Index: issue.Index,
|
||||
Issue: convert.ToAPIIssue(ctx, doer, issue),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
Changes: &api.ChangesPayload{
|
||||
AddedLabels: addedAPILabels,
|
||||
RemovedLabels: removedAPILabels,
|
||||
},
|
||||
}
|
||||
|
||||
newNotifyInputFromIssue(issue, event).
|
||||
WithDoer(doer).
|
||||
WithPayload(&api.IssuePayload{
|
||||
Action: action,
|
||||
Index: issue.Index,
|
||||
Issue: convert.ToAPIIssue(ctx, doer, issue),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
}).
|
||||
WithPayload(payload).
|
||||
Notify(ctx)
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Rep
|
||||
|
||||
// Get diff stats for commit
|
||||
if opts.Stat {
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(gitRepo, "", commit.ID.String())
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, repo, gitRepo, "", commit.ID.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
||||
// Calculate diff
|
||||
startCommitID = pr.MergeBase
|
||||
|
||||
diffShortStats, err := gitdiff.GetDiffShortStat(gitRepo, startCommitID, endCommitID)
|
||||
diffShortStats, err := gitdiff.GetDiffShortStat(ctx, pr.BaseRepo, gitRepo, startCommitID, endCommitID)
|
||||
if err != nil {
|
||||
log.Error("GetDiffShortStat: %v", err)
|
||||
} else {
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/sync"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
|
||||
"github.com/go-co-op/gocron"
|
||||
@ -19,13 +18,10 @@ import (
|
||||
|
||||
var scheduler = gocron.NewScheduler(time.Local)
|
||||
|
||||
// Prevent duplicate running tasks.
|
||||
var taskStatusTable = sync.NewStatusTable()
|
||||
|
||||
// NewContext begins cron tasks
|
||||
// Init begins cron tasks
|
||||
// Each cron task is run within the shutdown context as a running server
|
||||
// AtShutdown the cron server is stopped
|
||||
func NewContext(original context.Context) {
|
||||
func Init(original context.Context) {
|
||||
defer pprof.SetGoroutineLabels(original)
|
||||
_, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().ShutdownContext(), "Service: Cron", process.SystemProcessType, true)
|
||||
initBasicTasks()
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/globallock"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
@ -71,20 +72,30 @@ func (t *Task) Run() {
|
||||
}, t.config)
|
||||
}
|
||||
|
||||
func getCronTaskLockKey(name string) string {
|
||||
return "cron_task:" + name
|
||||
}
|
||||
|
||||
// RunWithUser will run the task incrementing the cron counter at the time with User
|
||||
func (t *Task) RunWithUser(doer *user_model.User, config Config) {
|
||||
if !taskStatusTable.StartIfNotRunning(t.Name) {
|
||||
locked, releaser, err := globallock.TryLock(graceful.GetManager().ShutdownContext(), getCronTaskLockKey(t.Name))
|
||||
if err != nil {
|
||||
log.Error("Failed to acquire lock for cron task %q: %v", t.Name, err)
|
||||
return
|
||||
}
|
||||
if !locked {
|
||||
log.Trace("a cron task %q is already running", t.Name)
|
||||
return
|
||||
}
|
||||
defer releaser()
|
||||
|
||||
t.lock.Lock()
|
||||
if config == nil {
|
||||
config = t.config
|
||||
}
|
||||
t.ExecTimes++
|
||||
t.lock.Unlock()
|
||||
defer func() {
|
||||
taskStatusTable.Stop(t.Name)
|
||||
}()
|
||||
|
||||
graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
|
@ -21,12 +21,14 @@ import (
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/analyze"
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/attribute"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/highlight"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@ -1271,9 +1273,7 @@ type DiffShortStat struct {
|
||||
NumFiles, TotalAddition, TotalDeletion int
|
||||
}
|
||||
|
||||
func GetDiffShortStat(gitRepo *git.Repository, beforeCommitID, afterCommitID string) (*DiffShortStat, error) {
|
||||
repoPath := gitRepo.Path
|
||||
|
||||
func GetDiffShortStat(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, beforeCommitID, afterCommitID string) (*DiffShortStat, error) {
|
||||
afterCommit, err := gitRepo.GetCommit(afterCommitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -1285,7 +1285,7 @@ func GetDiffShortStat(gitRepo *git.Repository, beforeCommitID, afterCommitID str
|
||||
}
|
||||
|
||||
diff := &DiffShortStat{}
|
||||
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStatByCmdArgs(gitRepo.Ctx, repoPath, nil, actualBeforeCommitID.String(), afterCommitID)
|
||||
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = gitrepo.GetDiffShortStatByCmdArgs(ctx, repo, nil, actualBeforeCommitID.String(), afterCommitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
)
|
||||
|
||||
@ -116,7 +117,7 @@ func isValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User,
|
||||
}
|
||||
}
|
||||
|
||||
lastReview, err := issues_model.GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
|
||||
lastReview, err := issues_model.GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID, optional.None[bool]())
|
||||
if err != nil && !issues_model.IsErrReviewNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ var (
|
||||
|
||||
func markPullRequestStatusAsChecking(ctx context.Context, pr *issues_model.PullRequest) bool {
|
||||
pr.Status = issues_model.PullRequestStatusChecking
|
||||
err := pr.UpdateColsIfNotMerged(ctx, "status")
|
||||
_, err := pr.UpdateColsIfNotMerged(ctx, "status")
|
||||
if err != nil {
|
||||
log.Error("UpdateColsIfNotMerged failed, pr: %-v, err: %v", pr, err)
|
||||
return false
|
||||
@ -256,7 +256,7 @@ func markPullRequestAsMergeable(ctx context.Context, pr *issues_model.PullReques
|
||||
return
|
||||
}
|
||||
|
||||
if err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files"); err != nil {
|
||||
if _, err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files"); err != nil {
|
||||
log.Error("Update[%-v]: %v", pr, err)
|
||||
}
|
||||
|
||||
|
@ -100,13 +100,6 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
divergence, err := git.GetDivergingCommits(ctx, prCtx.tmpBasePath, baseBranch, trackingBranch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pr.CommitsAhead = divergence.Ahead
|
||||
pr.CommitsBehind = divergence.Behind
|
||||
|
||||
assigneeCommentMap := make(map[int64]*issues_model.Comment)
|
||||
|
||||
var reviewNotifiers []*issue_service.ReviewRequestNotifier
|
||||
@ -135,6 +128,12 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update Commit Divergence
|
||||
err = syncCommitDivergence(ctx, pr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add first push codes comment
|
||||
if _, err := CreatePushPullComment(ctx, issue.Poster, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName(), false); err != nil {
|
||||
return err
|
||||
@ -288,25 +287,21 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer
|
||||
pr.Status = issues_model.PullRequestStatusMergeable
|
||||
}
|
||||
|
||||
// Update Commit Divergence
|
||||
divergence, err := GetDiverging(ctx, pr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pr.CommitsAhead = divergence.Ahead
|
||||
pr.CommitsBehind = divergence.Behind
|
||||
|
||||
// add first push codes comment
|
||||
baseGitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer baseGitRepo.Close()
|
||||
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files", "base_branch", "commits_ahead", "commits_behind"); err != nil {
|
||||
// The UPDATE acquires the transaction lock, if the UPDATE succeeds, it should have updated one row (the "base_branch" is changed)
|
||||
// If no row is updated, it means the PR has been merged or closed in the meantime
|
||||
updated, err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files", "base_branch")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if updated == 0 {
|
||||
return util.ErrorWrap(util.ErrInvalidArgument, "pull request status has changed")
|
||||
}
|
||||
|
||||
if err := syncCommitDivergence(ctx, pr); err != nil {
|
||||
return fmt.Errorf("syncCommitDivergence: %w", err)
|
||||
}
|
||||
|
||||
// Create comment
|
||||
options := &issues_model.CreateCommentOptions{
|
||||
@ -345,7 +340,7 @@ func checkForInvalidation(ctx context.Context, requests issues_model.PullRequest
|
||||
}
|
||||
go func() {
|
||||
// FIXME: graceful: We need to tell the manager we're doing something...
|
||||
err := InvalidateCodeComments(ctx, requests, doer, gitRepo, branch)
|
||||
err := InvalidateCodeComments(ctx, requests, doer, repo, gitRepo, branch)
|
||||
if err != nil {
|
||||
log.Error("PullRequestList.InvalidateCodeComments: %v", err)
|
||||
}
|
||||
@ -373,15 +368,21 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
|
||||
// If you don't let it run all the way then you will lose data
|
||||
// TODO: graceful: AddTestPullRequestTask needs to become a queue!
|
||||
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, opts.RepoID)
|
||||
if err != nil {
|
||||
log.Error("GetRepositoryByID: %v", err)
|
||||
return
|
||||
}
|
||||
// GetUnmergedPullRequestsByHeadInfo() only return open and unmerged PR.
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, opts.RepoID, opts.Branch)
|
||||
headBranchPRs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, opts.RepoID, opts.Branch)
|
||||
if err != nil {
|
||||
log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", opts.RepoID, opts.Branch, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, pr := range prs {
|
||||
for _, pr := range headBranchPRs {
|
||||
log.Trace("Updating PR[%d]: composing new test task", pr.ID)
|
||||
pr.HeadRepo = repo // avoid loading again
|
||||
if pr.Flow == issues_model.PullRequestFlowGithub {
|
||||
if err := PushToBaseRepo(ctx, pr); err != nil {
|
||||
log.Error("PushToBaseRepo: %v", err)
|
||||
@ -399,14 +400,14 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
|
||||
}
|
||||
|
||||
if opts.IsSync {
|
||||
if err = prs.LoadAttributes(ctx); err != nil {
|
||||
if err = headBranchPRs.LoadAttributes(ctx); err != nil {
|
||||
log.Error("PullRequestList.LoadAttributes: %v", err)
|
||||
}
|
||||
if invalidationErr := checkForInvalidation(ctx, prs, opts.RepoID, opts.Doer, opts.Branch); invalidationErr != nil {
|
||||
if invalidationErr := checkForInvalidation(ctx, headBranchPRs, opts.RepoID, opts.Doer, opts.Branch); invalidationErr != nil {
|
||||
log.Error("checkForInvalidation: %v", invalidationErr)
|
||||
}
|
||||
if err == nil {
|
||||
for _, pr := range prs {
|
||||
for _, pr := range headBranchPRs {
|
||||
objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName)
|
||||
if opts.NewCommitID != "" && opts.NewCommitID != objectFormat.EmptyObjectID().String() {
|
||||
changed, err := checkIfPRContentChanged(ctx, pr, opts.OldCommitID, opts.NewCommitID)
|
||||
@ -433,14 +434,8 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
|
||||
if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, opts.NewCommitID); err != nil {
|
||||
log.Error("MarkReviewsAsNotStale: %v", err)
|
||||
}
|
||||
divergence, err := GetDiverging(ctx, pr)
|
||||
if err != nil {
|
||||
log.Error("GetDiverging: %v", err)
|
||||
} else {
|
||||
err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
|
||||
if err != nil {
|
||||
log.Error("UpdateCommitDivergence: %v", err)
|
||||
}
|
||||
if err = syncCommitDivergence(ctx, pr); err != nil {
|
||||
log.Error("syncCommitDivergence: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -460,24 +455,22 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
|
||||
}
|
||||
|
||||
log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", opts.RepoID, opts.Branch)
|
||||
prs, err = issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, opts.RepoID, opts.Branch)
|
||||
// The base repositories of baseBranchPRs are the same one (opts.RepoID)
|
||||
baseBranchPRs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, opts.RepoID, opts.Branch)
|
||||
if err != nil {
|
||||
log.Error("Find pull requests [base_repo_id: %d, base_branch: %s]: %v", opts.RepoID, opts.Branch, err)
|
||||
return
|
||||
}
|
||||
for _, pr := range prs {
|
||||
divergence, err := GetDiverging(ctx, pr)
|
||||
for _, pr := range baseBranchPRs {
|
||||
pr.BaseRepo = repo // avoid loading again
|
||||
err = syncCommitDivergence(ctx, pr)
|
||||
if err != nil {
|
||||
if git_model.IsErrBranchNotExist(err) && !gitrepo.IsBranchExist(ctx, pr.HeadRepo, pr.HeadBranch) {
|
||||
log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
log.Warn("Cannot test PR %s/%d with base=%s head=%s: no longer exists", pr.BaseRepo.FullName(), pr.IssueID, pr.BaseBranch, pr.HeadBranch)
|
||||
} else {
|
||||
log.Error("GetDiverging: %v", err)
|
||||
}
|
||||
} else {
|
||||
err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
|
||||
if err != nil {
|
||||
log.Error("UpdateCommitDivergence: %v", err)
|
||||
log.Error("syncCommitDivergence: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
StartPullRequestCheckDelayable(ctx, pr)
|
||||
}
|
||||
@ -487,7 +480,7 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
|
||||
// checkIfPRContentChanged checks if diff to target branch has changed by push
|
||||
// A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged
|
||||
func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) {
|
||||
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
|
||||
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr) // FIXME: why it still needs to create a temp repo, since the alongside calls like GetDiverging doesn't do so anymore
|
||||
if err != nil {
|
||||
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
|
||||
return false, err
|
||||
|
@ -47,11 +47,25 @@ func (err ErrDismissRequestOnClosedPR) Unwrap() error {
|
||||
// ErrSubmitReviewOnClosedPR represents an error when an user tries to submit an approve or reject review associated to a closed or merged PR.
|
||||
var ErrSubmitReviewOnClosedPR = errors.New("can't submit review for a closed or merged PR")
|
||||
|
||||
// LineBlame returns the latest commit at the given line
|
||||
func lineBlame(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, branch, file string, line uint) (*git.Commit, error) {
|
||||
sha, err := gitrepo.LineBlame(ctx, repo, branch, file, line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(sha) < 40 {
|
||||
return nil, fmt.Errorf("invalid result of blame: %s", sha)
|
||||
}
|
||||
|
||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
|
||||
return gitRepo.GetCommit(sha[:objectFormat.FullLength()])
|
||||
}
|
||||
|
||||
// checkInvalidation checks if the line of code comment got changed by another commit.
|
||||
// If the line got changed the comment is going to be invalidated.
|
||||
func checkInvalidation(ctx context.Context, c *issues_model.Comment, repo *git.Repository, branch string) error {
|
||||
func checkInvalidation(ctx context.Context, c *issues_model.Comment, repo *repo_model.Repository, gitRepo *git.Repository, branch string) error {
|
||||
// FIXME differentiate between previous and proposed line
|
||||
commit, err := repo.LineBlame(branch, repo.Path, c.TreePath, uint(c.UnsignedLine()))
|
||||
commit, err := lineBlame(ctx, repo, gitRepo, branch, c.TreePath, uint(c.UnsignedLine()))
|
||||
if err != nil && (strings.Contains(err.Error(), "fatal: no such path") || notEnoughLines.MatchString(err.Error())) {
|
||||
c.Invalidated = true
|
||||
return issues_model.UpdateCommentInvalidate(ctx, c)
|
||||
@ -67,7 +81,7 @@ func checkInvalidation(ctx context.Context, c *issues_model.Comment, repo *git.R
|
||||
}
|
||||
|
||||
// InvalidateCodeComments will lookup the prs for code comments which got invalidated by change
|
||||
func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestList, doer *user_model.User, repo *git.Repository, branch string) error {
|
||||
func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestList, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branch string) error {
|
||||
if len(prs) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -83,7 +97,7 @@ func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestLis
|
||||
return fmt.Errorf("find code comments: %v", err)
|
||||
}
|
||||
for _, comment := range codeComments {
|
||||
if err := checkInvalidation(ctx, comment, repo, branch); err != nil {
|
||||
if err := checkInvalidation(ctx, comment, repo, gitRepo, branch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -233,7 +247,7 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo
|
||||
// FIXME validate treePath
|
||||
// Get latest commit referencing the commented line
|
||||
// No need for get commit for base branch changes
|
||||
commit, err := gitRepo.LineBlame(head, gitRepo.Path, treePath, uint(line))
|
||||
commit, err := lineBlame(ctx, pr.BaseRepo, gitRepo, head, treePath, uint(line))
|
||||
if err == nil {
|
||||
commitID = commit.ID.String()
|
||||
} else if !(strings.Contains(err.Error(), "exit status 128 - fatal: no such path") || notEnoughLines.MatchString(err.Error())) {
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/globallock"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/repository"
|
||||
@ -34,17 +34,21 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
|
||||
}
|
||||
defer releaser()
|
||||
|
||||
diffCount, err := GetDiverging(ctx, pr)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if diffCount.Behind == 0 {
|
||||
return fmt.Errorf("HeadBranch of PR %d is up to date", pr.Index)
|
||||
}
|
||||
|
||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||
log.Error("unable to load BaseRepo for %-v during update-by-merge: %v", pr, err)
|
||||
return fmt.Errorf("unable to load BaseRepo for PR[%d] during update-by-merge: %w", pr.ID, err)
|
||||
}
|
||||
|
||||
// TODO: FakePR: if the PR is a fake PR (for example: from Merge Upstream), then no need to check diverging
|
||||
if pr.ID > 0 {
|
||||
diffCount, err := gitrepo.GetDivergingCommits(ctx, pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
||||
if err != nil {
|
||||
return err
|
||||
} else if diffCount.Behind == 0 {
|
||||
return fmt.Errorf("HeadBranch of PR %d is up to date", pr.Index)
|
||||
}
|
||||
}
|
||||
|
||||
if err := pr.LoadHeadRepo(ctx); err != nil {
|
||||
log.Error("unable to load HeadRepo for PR %-v during update-by-merge: %v", pr, err)
|
||||
return fmt.Errorf("unable to load HeadRepo for PR[%d] during update-by-merge: %w", pr.ID, err)
|
||||
@ -172,18 +176,13 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest,
|
||||
return mergeAllowed, rebaseAllowed, nil
|
||||
}
|
||||
|
||||
// GetDiverging determines how many commits a PR is ahead or behind the PR base branch
|
||||
func GetDiverging(ctx context.Context, pr *issues_model.PullRequest) (*git.DivergeObject, error) {
|
||||
log.Trace("GetDiverging[%-v]: compare commits", pr)
|
||||
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
|
||||
if err != nil {
|
||||
if !git_model.IsErrBranchNotExist(err) {
|
||||
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
|
||||
}
|
||||
return nil, err
|
||||
func syncCommitDivergence(ctx context.Context, pr *issues_model.PullRequest) error {
|
||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
diff, err := git.GetDivergingCommits(ctx, prCtx.tmpBasePath, baseBranch, trackingBranch)
|
||||
return &diff, err
|
||||
divergence, err := gitrepo.GetDivergingCommits(ctx, pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ import (
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
release_service "code.gitea.io/gitea/services/release"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@ -123,9 +122,9 @@ func getDivergenceCacheKey(repoID int64, branchName string) string {
|
||||
}
|
||||
|
||||
// getDivergenceFromCache gets the divergence from cache
|
||||
func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject, bool) {
|
||||
func getDivergenceFromCache(repoID int64, branchName string) (*gitrepo.DivergeObject, bool) {
|
||||
data, ok := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName))
|
||||
res := git.DivergeObject{
|
||||
res := gitrepo.DivergeObject{
|
||||
Ahead: -1,
|
||||
Behind: -1,
|
||||
}
|
||||
@ -139,7 +138,7 @@ func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject
|
||||
return &res, true
|
||||
}
|
||||
|
||||
func putDivergenceFromCache(repoID int64, branchName string, divergence *git.DivergeObject) error {
|
||||
func putDivergenceFromCache(repoID int64, branchName string, divergence *gitrepo.DivergeObject) error {
|
||||
bs, err := json.Marshal(divergence)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -178,7 +177,7 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g
|
||||
p := protectedBranches.GetFirstMatched(branchName)
|
||||
isProtected := p != nil
|
||||
|
||||
var divergence *git.DivergeObject
|
||||
var divergence *gitrepo.DivergeObject
|
||||
|
||||
// it's not default branch
|
||||
if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted {
|
||||
@ -186,9 +185,9 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g
|
||||
divergence, cached = getDivergenceFromCache(repo.ID, dbBranch.Name)
|
||||
if !cached {
|
||||
var err error
|
||||
divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName)
|
||||
divergence, err = gitrepo.GetDivergingCommits(ctx, repo, repo.DefaultBranch, git.BranchPrefix+branchName)
|
||||
if err != nil {
|
||||
log.Error("CountDivergingCommits: %v", err)
|
||||
log.Error("GetDivergingCommits: %v", err)
|
||||
} else {
|
||||
if err = putDivergenceFromCache(repo.ID, dbBranch.Name, divergence); err != nil {
|
||||
log.Error("putDivergenceFromCache: %v", err)
|
||||
@ -199,7 +198,7 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g
|
||||
|
||||
if divergence == nil {
|
||||
// tolerate the error that we cannot get divergence
|
||||
divergence = &git.DivergeObject{Ahead: -1, Behind: -1}
|
||||
divergence = &gitrepo.DivergeObject{Ahead: -1, Behind: -1}
|
||||
}
|
||||
|
||||
pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx, repo.ID, branchName)
|
||||
@ -442,7 +441,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
|
||||
}
|
||||
|
||||
if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error {
|
||||
err2 := gitRepo.RenameBranch(from, to)
|
||||
err2 := gitrepo.RenameBranch(ctx, repo, from, to)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
@ -553,9 +552,7 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
|
||||
return nil
|
||||
}
|
||||
|
||||
return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
|
||||
Force: true,
|
||||
})
|
||||
return gitrepo.DeleteBranch(ctx, repo, branchName, true)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -720,7 +717,7 @@ func GetBranchDivergingInfo(ctx reqctx.RequestContext, baseRepo *repo_model.Repo
|
||||
// if the fork repo has new commits, this call will fail because they are not in the base repo
|
||||
// exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
|
||||
// so at the moment, we first check the update time, then check whether the fork branch has base's head
|
||||
diff, err := git.GetDivergingCommits(ctx, baseRepo.RepoPath(), baseGitBranch.CommitID, headGitBranch.CommitID)
|
||||
diff, err := gitrepo.GetDivergingCommits(ctx, baseRepo, baseGitBranch.CommitID, headGitBranch.CommitID)
|
||||
if err != nil {
|
||||
info.BaseHasNewCommits = baseGitBranch.UpdatedUnix > headGitBranch.UpdatedUnix
|
||||
if headRepo.IsFork && info.BaseHasNewCommits {
|
||||
|
@ -6,21 +6,11 @@ package files
|
||||
import (
|
||||
"context"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
)
|
||||
|
||||
// CountDivergingCommits determines how many commits a branch is ahead or behind the repository's base branch
|
||||
func CountDivergingCommits(ctx context.Context, repo *repo_model.Repository, branch string) (*git.DivergeObject, error) {
|
||||
divergence, err := git.GetDivergingCommits(ctx, repo.RepoPath(), repo.DefaultBranch, branch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &divergence, nil
|
||||
}
|
||||
|
||||
// GetPayloadCommitVerification returns the verification information of a commit
|
||||
func GetPayloadCommitVerification(ctx context.Context, commit *git.Commit) *structs.PayloadCommitVerification {
|
||||
verification := &structs.PayloadCommitVerification{}
|
||||
|
@ -27,8 +27,8 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func cloneWiki(ctx context.Context, u *user_model.User, opts migration.MigrateOptions, migrateTimeout time.Duration) (string, error) {
|
||||
wikiPath := repo_model.WikiPath(u.Name, opts.RepoName)
|
||||
func cloneWiki(ctx context.Context, repo *repo_model.Repository, opts migration.MigrateOptions, migrateTimeout time.Duration) (string, error) {
|
||||
wikiPath := repo.WikiPath()
|
||||
wikiRemotePath := repo_module.WikiRemoteURL(ctx, opts.CloneAddr)
|
||||
if wikiRemotePath == "" {
|
||||
return "", nil
|
||||
@ -59,7 +59,7 @@ func cloneWiki(ctx context.Context, u *user_model.User, opts migration.MigrateOp
|
||||
return "", err
|
||||
}
|
||||
|
||||
defaultBranch, err := git.GetDefaultBranch(ctx, wikiPath)
|
||||
defaultBranch, err := gitrepo.GetDefaultBranch(ctx, repo.WikiStorageRepo())
|
||||
if err != nil {
|
||||
cleanIncompleteWikiPath()
|
||||
return "", fmt.Errorf("failed to get wiki repo default branch for %q, err: %w", wikiPath, err)
|
||||
@ -73,7 +73,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
||||
repo *repo_model.Repository, opts migration.MigrateOptions,
|
||||
httpTransport *http.Transport,
|
||||
) (*repo_model.Repository, error) {
|
||||
repoPath := repo_model.RepoPath(u.Name, opts.RepoName)
|
||||
repoPath := repo.RepoPath()
|
||||
|
||||
if u.IsOrganization() {
|
||||
t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
|
||||
@ -108,7 +108,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
||||
}
|
||||
|
||||
if opts.Wiki {
|
||||
defaultWikiBranch, err := cloneWiki(ctx, u, opts, migrateTimeout)
|
||||
defaultWikiBranch, err := cloneWiki(ctx, repo, opts, migrateTimeout)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("clone wiki error: %w", err)
|
||||
}
|
||||
@ -137,7 +137,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
|
||||
if !repo.IsEmpty {
|
||||
if len(repo.DefaultBranch) == 0 {
|
||||
// Try to get HEAD branch and set it as default branch.
|
||||
headBranchName, err := git.GetDefaultBranch(ctx, repoPath)
|
||||
headBranchName, err := gitrepo.GetDefaultBranch(ctx, repo)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("GetHEADBranch: %w", err)
|
||||
}
|
||||
|
@ -131,24 +131,74 @@ func TestWebhookDeliverHookTask(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
done := make(chan struct{}, 1)
|
||||
version2Body := `{
|
||||
"body": "[[test/repo](http://localhost:3000/test/repo)] user1 pushed 2 commits to [test](http://localhost:3000/test/repo/src/branch/test):\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778): commit message - user1",
|
||||
"msgtype": "",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] user1 pushed 2 commits to <a href=\"http://localhost:3000/test/repo/src/branch/test\">test</a>:<br><a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>: commit message - user1<br><a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>: commit message - user1",
|
||||
"io.gitea.commits": [
|
||||
{
|
||||
"id": "2020558fe2e34debb818a514715839cabd25e778",
|
||||
"message": "commit message",
|
||||
"url": "http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778",
|
||||
"author": {
|
||||
"name": "user1",
|
||||
"email": "user1@localhost",
|
||||
"username": "user1"
|
||||
},
|
||||
"committer": {
|
||||
"name": "user1",
|
||||
"email": "user1@localhost",
|
||||
"username": "user1"
|
||||
},
|
||||
"verification": null,
|
||||
"timestamp": "0001-01-01T00:00:00Z",
|
||||
"added": null,
|
||||
"removed": null,
|
||||
"modified": null
|
||||
},
|
||||
{
|
||||
"id": "2020558fe2e34debb818a514715839cabd25e778",
|
||||
"message": "commit message",
|
||||
"url": "http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778",
|
||||
"author": {
|
||||
"name": "user1",
|
||||
"email": "user1@localhost",
|
||||
"username": "user1"
|
||||
},
|
||||
"committer": {
|
||||
"name": "user1",
|
||||
"email": "user1@localhost",
|
||||
"username": "user1"
|
||||
},
|
||||
"verification": null,
|
||||
"timestamp": "0001-01-01T00:00:00Z",
|
||||
"added": null,
|
||||
"removed": null,
|
||||
"modified": null
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
testVersion := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "PUT", r.Method)
|
||||
switch r.URL.Path {
|
||||
case "/webhook/66d222a5d6349e1311f551e50722d837e30fce98":
|
||||
// Version 1
|
||||
assert.True(t, strings.HasPrefix(r.URL.Path, "/webhook/"))
|
||||
assert.Len(t, r.URL.Path, len("/webhook/")+40) // +40 for txnID, a unique ID from payload's sha1 hash
|
||||
switch testVersion {
|
||||
case 1: // Version 1
|
||||
assert.Equal(t, "push", r.Header.Get("X-GitHub-Event"))
|
||||
assert.Empty(t, r.Header.Get("Content-Type"))
|
||||
body, err := io.ReadAll(r.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"data": 42}`, string(body))
|
||||
|
||||
case "/webhook/6db5dc1e282529a8c162c7fe93dd2667494eeb51":
|
||||
// Version 2
|
||||
case 2: // Version 2
|
||||
assert.Equal(t, "push", r.Header.Get("X-GitHub-Event"))
|
||||
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
||||
body, err := io.ReadAll(r.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, body, 2147)
|
||||
assert.JSONEq(t, version2Body, string(body))
|
||||
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
@ -172,6 +222,7 @@ func TestWebhookDeliverHookTask(t *testing.T) {
|
||||
assert.NoError(t, webhook_model.CreateWebhook(t.Context(), hook))
|
||||
|
||||
t.Run("Version 1", func(t *testing.T) {
|
||||
testVersion = 1
|
||||
hookTask := &webhook_model.HookTask{
|
||||
HookID: hook.ID,
|
||||
EventType: webhook_module.HookEventPush,
|
||||
@ -198,6 +249,7 @@ func TestWebhookDeliverHookTask(t *testing.T) {
|
||||
data, err := p.JSONPayload()
|
||||
assert.NoError(t, err)
|
||||
|
||||
testVersion = 2
|
||||
hookTask := &webhook_model.HookTask{
|
||||
HookID: hook.ID,
|
||||
EventType: webhook_module.HookEventPush,
|
||||
|
@ -274,6 +274,7 @@ func getMessageBody(htmlText string) string {
|
||||
|
||||
// getMatrixTxnID computes the transaction ID to ensure idempotency
|
||||
func getMatrixTxnID(payload []byte) (string, error) {
|
||||
payload = bytes.TrimSpace(payload)
|
||||
if len(payload) >= matrixPayloadSizeLimit {
|
||||
return "", fmt.Errorf("getMatrixTxnID: payload size %d > %d", len(payload), matrixPayloadSizeLimit)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
webhook_model "code.gitea.io/gitea/models/webhook"
|
||||
@ -216,7 +217,9 @@ func TestMatrixJSONPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "PUT", req.Method)
|
||||
assert.Equal(t, "/_matrix/client/r0/rooms/ROOM_ID/send/m.room.message/6db5dc1e282529a8c162c7fe93dd2667494eeb51", req.URL.Path)
|
||||
txnID, ok := strings.CutPrefix(req.URL.Path, "/_matrix/client/r0/rooms/ROOM_ID/send/m.room.message/")
|
||||
assert.True(t, ok)
|
||||
assert.Len(t, txnID, 40) // txnID is just a unique ID for a webhook request, it is a sha1 hash from the payload
|
||||
assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
|
||||
assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
|
||||
var body MatrixPayload
|
||||
|
@ -6,7 +6,6 @@ package wiki
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@ -22,7 +21,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
)
|
||||
@ -393,15 +391,7 @@ func ChangeDefaultWikiBranch(ctx context.Context, repo *repo_model.Repository, n
|
||||
return nil
|
||||
}
|
||||
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo.WikiStorageRepo())
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
return nil // no git repo on storage, no need to do anything else
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("unable to open repository: %w", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
err = gitRepo.RenameBranch(oldDefBranch, newBranch)
|
||||
err = gitrepo.RenameBranch(ctx, repo.WikiStorageRepo(), oldDefBranch, newBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename default branch: %w", err)
|
||||
}
|
||||
|
@ -168,10 +168,6 @@
|
||||
{{template "repo/issue/view_content/reference_issue_dialog" .}}
|
||||
{{template "shared/user/block_user_dialog" .}}
|
||||
|
||||
<div class="tw-hidden" id="no-content">
|
||||
<span class="no-content">{{ctx.Locale.Tr "repo.issues.no_content"}}</span>
|
||||
</div>
|
||||
|
||||
<div class="ui g-modal-confirm delete modal">
|
||||
<div class="header">
|
||||
{{svg "octicon-trash"}}
|
||||
|
2
templates/swagger/v1_json.tmpl
generated
2
templates/swagger/v1_json.tmpl
generated
@ -9109,7 +9109,7 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded",
|
||||
"description": "comma separated list of label names. Fetch only issues that have any of this label names. Non existent labels are discarded.",
|
||||
"name": "labels",
|
||||
"in": "query"
|
||||
},
|
||||
|
@ -318,7 +318,7 @@ func TestPackageSwift(t *testing.T) {
|
||||
AddBasicAuth(user.Name)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.Equal(t, body, resp.Body.String())
|
||||
assert.JSONEq(t, body, resp.Body.String())
|
||||
})
|
||||
|
||||
t.Run("PackageVersionMetadata", func(t *testing.T) {
|
||||
|
@ -121,10 +121,10 @@ func TestAPIRepoBranchesMirror(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusForbidden)
|
||||
bs, err = io.ReadAll(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs))
|
||||
assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs))
|
||||
|
||||
resp = MakeRequest(t, NewRequest(t, "DELETE", link2.String()).AddTokenAuth(token), http.StatusForbidden)
|
||||
bs, err = io.ReadAll(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs))
|
||||
assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs))
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -43,7 +43,7 @@ func TestChangeDefaultBranch(t *testing.T) {
|
||||
session.MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDefaultBranch string, expectedBranchToDivergence map[string]git.DivergeObject) {
|
||||
func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDefaultBranch string, expectedBranchToDivergence map[string]*gitrepo.DivergeObject) {
|
||||
req := NewRequest(t, "GET", branchesURL)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
@ -92,7 +92,7 @@ func TestChangeDefaultBranchDivergence(t *testing.T) {
|
||||
settingsBranchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name)
|
||||
|
||||
// check branch divergence before switching default branch
|
||||
expectedBranchToDivergenceBefore := map[string]git.DivergeObject{
|
||||
expectedBranchToDivergenceBefore := map[string]*gitrepo.DivergeObject{
|
||||
"not-signed": {
|
||||
Ahead: 0,
|
||||
Behind: 0,
|
||||
@ -119,7 +119,7 @@ func TestChangeDefaultBranchDivergence(t *testing.T) {
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
// check branch divergence after switching default branch
|
||||
expectedBranchToDivergenceAfter := map[string]git.DivergeObject{
|
||||
expectedBranchToDivergenceAfter := map[string]*gitrepo.DivergeObject{
|
||||
"master": {
|
||||
Ahead: 1,
|
||||
Behind: 0,
|
||||
|
@ -413,7 +413,8 @@ func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) {
|
||||
func DecodeJSON(t testing.TB, resp *httptest.ResponseRecorder, v any) {
|
||||
t.Helper()
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
// FIXME: JSON-KEY-CASE: for testing purpose only, because many structs don't provide `json` tags, they just use capitalized field names
|
||||
decoder := json.NewDecoderCaseInsensitive(resp.Body)
|
||||
require.NoError(t, decoder.Decode(v))
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,13 @@ import (
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPIPullUpdate(t *testing.T) {
|
||||
@ -27,14 +29,16 @@ func TestAPIPullUpdate(t *testing.T) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
|
||||
pr := createOutdatedPR(t, user, org26)
|
||||
require.NoError(t, pr.LoadBaseRepo(t.Context()))
|
||||
require.NoError(t, pr.LoadIssue(t.Context()))
|
||||
|
||||
// Test GetDiverging
|
||||
diffCount, err := pull_service.GetDiverging(t.Context(), pr)
|
||||
assert.NoError(t, err)
|
||||
diffCount, err := gitrepo.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, diffCount.Behind)
|
||||
assert.Equal(t, 1, diffCount.Ahead)
|
||||
assert.NoError(t, pr.LoadBaseRepo(t.Context()))
|
||||
assert.NoError(t, pr.LoadIssue(t.Context()))
|
||||
assert.Equal(t, diffCount.Behind, pr.CommitsBehind)
|
||||
assert.Equal(t, diffCount.Ahead, pr.CommitsAhead)
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
@ -43,10 +47,14 @@ func TestAPIPullUpdate(t *testing.T) {
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Test GetDiverging after update
|
||||
diffCount, err = pull_service.GetDiverging(t.Context(), pr)
|
||||
assert.NoError(t, err)
|
||||
diffCount, err = gitrepo.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, diffCount.Behind)
|
||||
assert.Equal(t, 2, diffCount.Ahead)
|
||||
assert.Eventually(t, func() bool {
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
||||
return diffCount.Behind == pr.CommitsBehind && diffCount.Ahead == pr.CommitsAhead
|
||||
}, 5*time.Second, 20*time.Millisecond)
|
||||
})
|
||||
}
|
||||
|
||||
@ -56,13 +64,13 @@ func TestAPIPullUpdateByRebase(t *testing.T) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
|
||||
pr := createOutdatedPR(t, user, org26)
|
||||
assert.NoError(t, pr.LoadBaseRepo(t.Context()))
|
||||
|
||||
// Test GetDiverging
|
||||
diffCount, err := pull_service.GetDiverging(t.Context(), pr)
|
||||
diffCount, err := gitrepo.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, diffCount.Behind)
|
||||
assert.Equal(t, 1, diffCount.Ahead)
|
||||
assert.NoError(t, pr.LoadBaseRepo(t.Context()))
|
||||
assert.NoError(t, pr.LoadIssue(t.Context()))
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
@ -72,7 +80,7 @@ func TestAPIPullUpdateByRebase(t *testing.T) {
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Test GetDiverging after update
|
||||
diffCount, err = pull_service.GetDiverging(t.Context(), pr)
|
||||
diffCount, err = gitrepo.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, diffCount.Behind)
|
||||
assert.Equal(t, 1, diffCount.Ahead)
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"include": [
|
||||
"${configDir}/.*",
|
||||
"${configDir}/*",
|
||||
"${configDir}/.*",
|
||||
"${configDir}/tests/e2e/**/*",
|
||||
"${configDir}/tests/e2e/**/.*",
|
||||
"${configDir}/tools/**/*",
|
||||
@ -17,27 +17,31 @@
|
||||
"allowImportingTsExtensions": true,
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"alwaysStrict": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"esModuleInterop": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"isolatedModules": true,
|
||||
"libReplacement": false,
|
||||
"noEmit": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"stripInternal": true,
|
||||
"sourceMap": true,
|
||||
"strict": false,
|
||||
"strictBindCallApply": true,
|
||||
"strictBuiltinIteratorReturn": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"sourceMap": true,
|
||||
"stripInternal": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"./web_src/js/globals.d.ts",
|
||||
|
@ -13,10 +13,10 @@ async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
|
||||
if (!clickTarget) return;
|
||||
|
||||
e.preventDefault();
|
||||
const segment = clickTarget.closest('.comment-header').nextElementSibling;
|
||||
const editContentZone = segment.querySelector('.edit-content-zone');
|
||||
const renderContent = segment.querySelector('.render-content');
|
||||
const rawContent = segment.querySelector('.raw-content');
|
||||
const commentContent = clickTarget.closest('.comment-header').nextElementSibling;
|
||||
const editContentZone = commentContent.querySelector('.edit-content-zone');
|
||||
let renderContent = commentContent.querySelector('.render-content');
|
||||
const rawContent = commentContent.querySelector('.raw-content');
|
||||
|
||||
let comboMarkdownEditor : ComboMarkdownEditor;
|
||||
|
||||
@ -47,30 +47,32 @@ async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
|
||||
|
||||
const response = await POST(editContentZone.getAttribute('data-update-url'), {data: params});
|
||||
const data = await response.json();
|
||||
if (response.status === 400) {
|
||||
showErrorToast(data.errorMessage);
|
||||
if (!response.ok) {
|
||||
showErrorToast(data?.errorMessage ?? window.config.i18n.error_occurred);
|
||||
return;
|
||||
}
|
||||
|
||||
reinitializeAreYouSure(editContentZone.querySelector('form')); // the form is no longer dirty
|
||||
editContentZone.setAttribute('data-content-version', data.contentVersion);
|
||||
if (!data.content) {
|
||||
renderContent.innerHTML = document.querySelector('#no-content').innerHTML;
|
||||
rawContent.textContent = '';
|
||||
} else {
|
||||
renderContent.innerHTML = data.content;
|
||||
rawContent.textContent = comboMarkdownEditor.value();
|
||||
const refIssues = renderContent.querySelectorAll<HTMLElement>('p .ref-issue');
|
||||
attachRefIssueContextPopup(refIssues);
|
||||
}
|
||||
const content = segment;
|
||||
if (!content.querySelector('.dropzone-attachments')) {
|
||||
|
||||
// replace the render content with new one, to trigger re-initialization of all features
|
||||
const newRenderContent = renderContent.cloneNode(false) as HTMLElement;
|
||||
newRenderContent.innerHTML = data.content;
|
||||
renderContent.replaceWith(newRenderContent);
|
||||
renderContent = newRenderContent;
|
||||
|
||||
rawContent.textContent = comboMarkdownEditor.value();
|
||||
const refIssues = renderContent.querySelectorAll<HTMLElement>('p .ref-issue');
|
||||
attachRefIssueContextPopup(refIssues);
|
||||
|
||||
if (!commentContent.querySelector('.dropzone-attachments')) {
|
||||
if (data.attachments !== '') {
|
||||
content.insertAdjacentHTML('beforeend', data.attachments);
|
||||
commentContent.insertAdjacentHTML('beforeend', data.attachments);
|
||||
}
|
||||
} else if (data.attachments === '') {
|
||||
content.querySelector('.dropzone-attachments').remove();
|
||||
commentContent.querySelector('.dropzone-attachments').remove();
|
||||
} else {
|
||||
content.querySelector('.dropzone-attachments').outerHTML = data.attachments;
|
||||
commentContent.querySelector('.dropzone-attachments').outerHTML = data.attachments;
|
||||
}
|
||||
comboMarkdownEditor.dropzoneSubmitReload();
|
||||
} catch (error) {
|
||||
|
@ -10,7 +10,7 @@ export function initTableSort() {
|
||||
}
|
||||
|
||||
function tableSort(normSort: string, revSort: string, isDefault: string) {
|
||||
if (!normSort) return false;
|
||||
if (!normSort) return;
|
||||
if (!revSort) revSort = '';
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
|
@ -43,7 +43,7 @@ type ToastOpts = {
|
||||
type ToastifyElement = HTMLElement & {_giteaToastifyInstance?: Toast};
|
||||
|
||||
/** See https://github.com/apvarun/toastify-js#api for options */
|
||||
function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast {
|
||||
function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast | null {
|
||||
const body = useHtmlBody ? message : htmlEscape(message);
|
||||
const parent = document.querySelector('.ui.dimmer.active') ?? document.body;
|
||||
const duplicateKey = preventDuplicates ? (preventDuplicates === true ? `${level}-${body}` : preventDuplicates) : '';
|
||||
@ -56,7 +56,7 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
|
||||
showElem(toastDupNumEl);
|
||||
toastDupNumEl.textContent = String(Number(toastDupNumEl.textContent) + 1);
|
||||
animateOnce(toastDupNumEl, 'pulse-1p5-200');
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,15 +83,15 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
|
||||
return toast;
|
||||
}
|
||||
|
||||
export function showInfoToast(message: string, opts?: ToastOpts): Toast {
|
||||
export function showInfoToast(message: string, opts?: ToastOpts): Toast | null {
|
||||
return showToast(message, 'info', opts);
|
||||
}
|
||||
|
||||
export function showWarningToast(message: string, opts?: ToastOpts): Toast {
|
||||
export function showWarningToast(message: string, opts?: ToastOpts): Toast | null {
|
||||
return showToast(message, 'warning', opts);
|
||||
}
|
||||
|
||||
export function showErrorToast(message: string, opts?: ToastOpts): Toast {
|
||||
export function showErrorToast(message: string, opts?: ToastOpts): Toast | null {
|
||||
return showToast(message, 'error', opts);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user