2
0

Compare commits

..

16 Commits

Author SHA1 Message Date
Lunny Xiao
470b21056a Add changelog for 1.25.1 and add missing chagnelog for 1.24.x (#35838) 2025-11-04 11:17:59 -08:00
Zettat123
61011f1648 Add a doctor command to fix inconsistent run status (#35840) (#35845)
Backport #35840

#35783 fixes an actions rerun bug. Due to this bug, some runs may be
incorrectly marked as `StatusWaiting` even though all the jobs are in
done status. These runs cannot be run or cancelled. This PR adds a new
doctor command to fix the inconsistent run status.

```
gitea doctor check --run fix-actions-unfinished-run-status --fix
```
2025-11-04 11:16:36 -08:00
Giteabot
7ea9722c1d Make ACME email optional (#35849) (#35857)
Backport #35849 by @wxiaoguang

Fix a regression from #33668

Fix #35847

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-04 10:22:26 -08:00
Giteabot
297f63af42 Remove wrong code (#35846) (#35856)
Backport #35846 by @lunny

Follow #35821
Fix https://github.com/go-gitea/gitea/pull/35844#issuecomment-3483521045

The reviewed file numbers and progress have been set from backend so
that we don't need to update the numbers when clicking `load more`.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-11-04 20:04:08 +02:00
Lunny Xiao
6a55749359 Fix incorrect pull request counter (#35819) (#35841)
Fix #35781, #27472
Backport #35819 

The PR will not correct the wrong numbers automatically.

There is a cron task `check_repo_stats` which will be run when Gitea
start or midnight. It will correct the numbers.
2025-11-04 01:49:47 +00:00
Lunny Xiao
8116742e2d Fix viewed files number is not right if not all files loaded (#35821) (#35844)
Fix #35803
Backport #35821

Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: silverwind <me@silverwind.io>
2025-11-03 17:19:50 -08:00
Giteabot
0a9cbf3228 upgrade go mail to 0.7.2 and fix the bug (#35833) (#35837)
Backport #35833 by @lunny

patch from
https://github.com/wneessen/go-mail/issues/504#issuecomment-3477890515.
Thanks to @wneessen

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-11-03 20:28:12 +00:00
Giteabot
74dfadb543 Fix circular spin animation direction (#35785) (#35823)
Backport #35785 by @lutinglt

Wait for the status icon to rotate clockwise instead of counterclockwise

before:
![GIF 2025-10-30
10-50-07](https://github.com/user-attachments/assets/3771b0bf-44e4-45a0-bde5-1b2b3dd8ba2a)

after:
![GIF 2025-10-30
10-50-43](https://github.com/user-attachments/assets/c45307fe-39a4-4e60-b48e-9d922c407565)

This can be merged to 1.25

Signed-off-by: 鲁汀 <131967983+lutinglt@users.noreply.github.com>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: 鲁汀 <131967983+lutinglt@users.noreply.github.com>
Co-authored-by: lutinglt <lutinglt@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-03 09:37:01 +00:00
Giteabot
8ffc1fbfbf Revert gomail to v0.7.0 to fix sending mail failed (#35816) (#35824)
Backport #35816 by @lunny

Revert gomail to the last work version to fix #35794

There is a problem between go mail v0.7.1 to prevent sending email work.
https://github.com/wneessen/go-mail/compare/v0.7.0...v0.7.1

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-11-03 09:08:13 +00:00
Giteabot
e95378329b Fix clone mixed bug (#35810) (#35822) 2025-11-02 10:20:27 -08:00
Giteabot
fddf6cd63f Fix cli "Before" handling (#35797) (#35808)
Backport #35797 by @wxiaoguang

Regression of #34973

Fix #35796

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-31 22:00:14 +01:00
Giteabot
d253e2055b Fix file extension on gogs.png (#35793) (#35799)
Backport #35793 by @silverwind

During https://github.com/go-gitea/gitea/issues/35790, it was noticed
that this PNG image had the wrong file extension. I also verified
`dingtalk.ico` and that one is actually an `.ico`.

Co-authored-by: silverwind <me@silverwind.io>
2025-10-31 03:33:27 +01:00
Giteabot
e194d89c74 Improve and fix markup code preview rendering (#35777) (#35787)
Backport #35777 by @silverwind

1. Add the color on the link to the referenced file, which is the more
likely thing the user wants to click
2. Use monospace font on the SHA
3. Tweak text colors
4. Change SHA link to go to the commit instead of the repo root with
commit filter set
5. Added the repo name to the file link text
6. Fix broken line numbering rendering

The only major difference to GitHub is now the missing line numbers.

Before:

<img width="286" height="162" alt="Screenshot 2025-10-29 at 19 09 59"
src="https://github.com/user-attachments/assets/f16b4eec-caf2-4c31-a2b5-ae5f41747d4b"
/>

After:

<img width="378" height="157" alt="image"
src="https://github.com/user-attachments/assets/0c91dfd3-0910-4b2d-a43b-8c87cfbb933e"
/>

For comparison, GitHub rendering:

<img width="286" height="177" alt="image"
src="https://github.com/user-attachments/assets/8a9a07b7-9153-4415-9d7a-5685853e472a"
/>

Co-authored-by: silverwind <me@silverwind.io>
2025-10-30 09:06:44 +00:00
Zettat123
04b6f90889 Fix actions rerun bug (#35783) (#35784)
Backport #35783

Fix #35780, fix #35782 

Rerunning a job or a run is only allowed when the job is done and the
run is done.

Related PR: #34970


98ff7d0773/routers/web/repo/actions/view.go (L239)

We don't need to check run status again in `rerunJob` because the run
status has been changed before `rerunJob`.

---

In fact, the bug described in the above issues will not occur on the
main branch. Because `getRunJobs` is called before updating the run.


98ff7d0773/routers/web/repo/actions/view.go (L425-L435)

So the run status that `rerunJob` checks is the old status.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-30 09:31:42 +01:00
Giteabot
65a37572f3 add pnpm to Snapcraft (#35778) (#35779)
Backport #35778 by techknowlogick

Co-authored-by: techknowlogick <techknowlogick@gitea.com>
2025-10-29 19:35:18 +01:00
Giteabot
f85cd7aeb5 Fix actions schedule update issue (#35767) (#35774)
Backport #35767 by @Zettat123

Fix #34472

Add integration tests for actions schedule update.

---------

Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-29 18:51:16 +01:00
60 changed files with 1137 additions and 128 deletions

View File

@@ -4,7 +4,26 @@ This changelog goes through the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.com).
## [1.25.0](https://github.com/go-gitea/gitea/releases/tag/1.25.0) - 2025-10-30
## [1.25.1](https://github.com/go-gitea/gitea/releases/tag/v1.25.1) - 2025-11-03
* BUGFIXES
* Make ACME email optional (#35849) #35857
* Add a doctor command to fix inconsistent run status (#35840) (#35845)
* Remove wrong code (#35846)
* Fix viewed files number is not right if not all files loaded (#35821) (#35844)
* Fix incorrect pull request counter (#35819) (#35841)
* Upgrade go mail to 0.7.2 and fix the bug (#35833) (#35837)
* Revert gomail to v0.7.0 to fix sending mail failed (#35816) (#35824)
* Fix clone mixed bug (#35810) (#35822)
* Fix cli "Before" handling (#35797) (#35808)
* Improve and fix markup code preview rendering (#35777) (#35787)
* Fix actions rerun bug (#35783) (#35784)
* Fix actions schedule update issue (#35767) (#35774)
* Fix circular spin animation direction (#35785) (#35823)
* Fix file extension on gogs.png (#35793) (#35799)
* Add pnpm to Snapcraft (#35778)
## [1.25.0](https://github.com/go-gitea/gitea/releases/tag/v1.25.0) - 2025-10-30
* BREAKING
* Return 201 Created for CreateVariable API responses (#34517)
@@ -231,7 +250,119 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Docs/fix typo and grammar in CONTRIBUTING.md (#35024)
* Improve english grammar and readability in locale_en-US.ini (#35017)
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/1.24.0) - 2025-05-26
## [1.24.7](https://github.com/go-gitea/gitea/releases/tag/v1.24.7) - 2025-10-24
* SECURITY
* Refactor legacy code (#35708) (#35713)
* Fixing issue #35530: Password Leak in Log Messages (#35584) (#35665)
* Fix a bug missed return (#35655) (#35671)
* BUGFIXES
* Fix inputing review comment will remove reviewer (#35591) (#35664)
* TESTING
* Mock external service in hcaptcha TestCaptcha (#35604) (#35663)
* Fix build (#35669)
## [1.24.6](https://github.com/go-gitea/gitea/releases/tag/v1.24.6) - 2025-09-10
* SECURITY
* Upgrade xz to v0.5.15 (#35385)
* BUGFIXES
* Fix a compare page 404 bug when the pull request disabled (#35441) (#35453)
* Fix bug when issue disabled, pull request number in the commit message cannot be redirected (#35420) (#35442)
* Add author.name field to Swift Package Registry API response (#35410) (#35431)
* Remove usernames when empty in discord webhook (#35412) (#35417)
* Allow foreachref parser to grow its buffer (#35365) (#35376)
* Allow deleting comment with content via API like web did (#35346) (#35354)
* Fix atom/rss mixed error (#35345) (#35347)
* Fix review request webhook bug (#35339)
* Remove duplicate html IDs (#35210) (#35325)
* Fix LFS range size header response (#35277) (#35293)
* Fix GitHub release assets URL validation (#35287) (#35290)
* Fix token lifetime, closes #35230 (#35271) (#35281)
* Fix push commits comments when changing the pull request target branch (#35386) (#35443)
## [1.24.5](https://github.com/go-gitea/gitea/releases/tag/v1.24.5) - 2025-08-12
* BUGFIXES
* Fix a bug where lfs gc never worked. (#35198) (#35255)
* Reload issue when sending webhook to make num comments is right. (#35243) (#35248)
* Fix bug when review pull request commits (#35192) (#35246)
* MISC
* Vertically center "Show Resolved" (#35211) (#35218)
## [1.24.4](https://github.com/go-gitea/gitea/releases/tag/v1.24.4) - 2025-08-03
* BUGFIXES
* Fix various bugs (1.24) (#35186)
* Fix migrate input box bug (#35166) (#35171)
* Only hide dropzone when no files have been uploaded (#35156) (#35167)
* Fix review comment/dimiss comment x reference can be refereced back (#35094) (#35099)
* Fix submodule nil check (#35096) (#35098)
* MISC
* Don't use full-file highlight when there is a git diff textconv (#35114) (#35119)
* Increase gap on latest commit (#35104) (#35113)
## [1.24.3](https://github.com/go-gitea/gitea/releases/tag/v1.24.3) - 2025-07-15
* BUGFIXES
* Fix form property assignment edge case (#35073) (#35078)
* Improve submodule relative path handling (#35056) (#35075)
* Fix incorrect comment diff hunk parsing, fix github asset ID nil panic (#35046) (#35055)
* Fix updating user visibility (#35036) (#35044)
* Support base64-encoded agit push options (#35037) (#35041)
* Make submodule link work with relative path (#35034) (#35038)
* Fix bug when displaying git user avatar in commits list (#35006)
* Fix API response for swagger spec (#35029)
* Start automerge check again after the conflict check and the schedule (#34988) (#35002)
* Fix the response format for actions/workflows (#35009) (#35016)
* Fix repo settings and protocol log problems (#35012) (#35013)
* Fix project images scroll (#34971) (#34972)
* Mark old reviews as stale on agit pr updates (#34933) (#34965)
* Fix git graph page (#34948) (#34949)
* Don't send trigger for a pending review's comment create/update/delete (#34928) (#34939)
* Fix some log and UI problems (#34863) (#34868)
* Fix archive API (#34853) (#34857)
* Ignore force pushes for changed files in a PR review (#34837) (#34843)
* Fix SSH LFS timeout (#34838) (#34842)
* Fix team permissions (#34827) (#34836)
* Fix job status aggregation logic (#34823) (#34835)
* Fix issue filter (#34914) (#34915)
* Fix typo in pull request merge warning message text (#34899) (#34903)
* Support the open-icon of folder (#34168) (#34896)
* Optimize flex layout of release attachment area (#34885) (#34886)
* Fix the issue of abnormal interface when there is no issue-item on the project page (#34791) (#34880)
* Skip updating timestamp when sync branch (#34875)
* Fix required contexts and commit status matching bug (#34815) (#34829)
## [1.24.2](https://github.com/go-gitea/gitea/releases/tag/v1.24.2) - 2025-06-20
* BUGFIXES
* Fix container range bug (#34795) (#34796)
* Upgrade chi to v5.2.2 (#34798) (#34799)
* BUILD
* Bump poetry feature to new url for dev container (#34787) (#34790)
## [1.24.1](https://github.com/go-gitea/gitea/releases/tag/v1.24.1) - 2025-06-18
* ENHANCEMENTS
* Improve alignment of commit status icon on commit page (#34750) (#34757)
* Support title and body query parameters for new PRs (#34537) (#34752)
* BUGFIXES
* When using rules to delete packages, remove unclean bugs (#34632) (#34761)
* Fix ghost user in feeds when pushing in an actions, it should be gitea-actions (#34703) (#34756)
* Prevent double markdown link brackets when pasting URL (#34745) (#34748)
* Prevent duplicate form submissions when creating forks (#34714) (#34735)
* Fix markdown wrap (#34697) (#34702)
* Fix pull requests API convert panic when head repository is deleted. (#34685) (#34687)
* Fix commit message rendering and some UI problems (#34680) (#34683)
* Fix container range bug (#34725) (#34732)
* Fix incorrect cli default values (#34765) (#34766)
* Fix dropdown filter (#34708) (#34711)
* Hide href attribute of a tag if there is no target_url (#34556) (#34684)
* Fix tag target (#34781) #34783
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/v1.24.0) - 2025-05-26
* BREAKING
* Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076)
@@ -601,7 +732,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Bump x/net (#32896) (#32900)
* Only activity tab needs heatmap data loading (#34652)
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/v1.23.8) - 2025-05-11
* SECURITY
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
@@ -628,7 +759,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Bump go version in go.mod (#34160)
* remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/v1.23.7) - 2025-04-07
* Enhancements
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
@@ -726,7 +857,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* BUGFIXES
* Fix a bug caused by status webhook template #33512
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/1.23.2) - 2025-02-04
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/v1.23.2) - 2025-02-04
* BREAKING
* Add tests for webhook and fix some webhook bugs (#33396) (#33442)
@@ -3256,7 +3387,7 @@ Key highlights of this release encompass significant changes categorized under `
* Improve decryption failure message (#24573) (#24575)
* Makefile: Use portable !, not GNUish -not, with find(1). (#24565) (#24572)
## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/1.19.3) - 2023-05-03
## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/v1.19.3) - 2023-05-03
* SECURITY
* Use golang 1.20.4 to fix CVE-2023-24539, CVE-2023-24540, and CVE-2023-29400
@@ -3269,7 +3400,7 @@ Key highlights of this release encompass significant changes categorized under `
* Fix incorrect CurrentUser check for docker rootless (#24435)
* Getting the tag list does not require being signed in (#24413) (#24416)
## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/1.19.2) - 2023-04-26
## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/v1.19.2) - 2023-04-26
* SECURITY
* Require repo scope for PATs for private repos and basic authentication (#24362) (#24364)
@@ -3768,7 +3899,7 @@ Key highlights of this release encompass significant changes categorized under `
* Display attachments of review comment when comment content is blank (#23035) (#23046)
* Return empty url for submodule tree entries (#23043) (#23048)
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/1.18.4) - 2023-02-20
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/v1.18.4) - 2023-02-20
* SECURITY
* Provide the ability to set password hash algorithm parameters (#22942) (#22943)
@@ -4195,7 +4326,7 @@ Key highlights of this release encompass significant changes categorized under `
* Fix the mode of custom dir to 0700 in docker-rootless (#20861) (#20867)
* Fix UI mis-align for PR commit history (#20845) (#20859)
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/v1.17.1) - 2022-08-17
* SECURITY
* Correctly escape within tribute.js (#20831) (#20832)

View File

@@ -121,6 +121,12 @@ func globalBool(c *cli.Command, name string) bool {
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cli.Command) (context.Context, error) {
return func(ctx context.Context, c *cli.Command) (context.Context, error) {
if setting.InstallLock {
// During config loading, there might also be logs (for example: deprecation warnings).
// It must make sure that console logger is set up before config is loaded.
log.Error("Config is loaded before console logger is setup, it will cause bugs. Please fix it.")
return nil, errors.New("console logger must be setup before config is loaded")
}
level := defaultLevel
if globalBool(c, "quiet") {
level = log.FATAL

View File

@@ -19,7 +19,7 @@ import (
var CmdKeys = &cli.Command{
Name: "keys",
Usage: "(internal) Should only be called by SSH server",
Hidden: true, // internal commands shouldn't not be visible
Hidden: true, // internal commands shouldn't be visible
Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runKeys,

View File

@@ -50,11 +50,15 @@ DEFAULT CONFIGURATION:
func prepareSubcommandWithGlobalFlags(originCmd *cli.Command) {
originBefore := originCmd.Before
originCmd.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
prepareWorkPathAndCustomConf(cmd)
originCmd.Before = func(ctxOrig context.Context, cmd *cli.Command) (ctx context.Context, err error) {
ctx = ctxOrig
if originBefore != nil {
return originBefore(ctx, cmd)
ctx, err = originBefore(ctx, cmd)
if err != nil {
return ctx, err
}
}
prepareWorkPathAndCustomConf(cmd)
return ctx, nil
}
}

View File

@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3"
@@ -28,11 +29,11 @@ func makePathOutput(workPath, customPath, customConf string) string {
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
}
func newTestApp(testCmdAction cli.ActionFunc) *cli.Command {
func newTestApp(testCmd cli.Command) *cli.Command {
app := NewMainApp(AppVersion{})
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
prepareSubcommandWithGlobalFlags(testCmd)
app.Commands = append(app.Commands, testCmd)
testCmd.Name = util.IfZero(testCmd.Name, "test-cmd")
prepareSubcommandWithGlobalFlags(&testCmd)
app.Commands = append(app.Commands, &testCmd)
app.DefaultCommand = testCmd.Name
return app
}
@@ -156,9 +157,11 @@ func TestCliCmd(t *testing.T) {
for _, c := range cases {
t.Run(c.cmd, func(t *testing.T) {
app := newTestApp(func(ctx context.Context, cmd *cli.Command) error {
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
app := newTestApp(cli.Command{
Action: func(ctx context.Context, cmd *cli.Command) error {
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
},
})
for k, v := range c.env {
t.Setenv(k, v)
@@ -173,31 +176,54 @@ func TestCliCmd(t *testing.T) {
}
func TestCliCmdError(t *testing.T) {
app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") })
app := newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }})
r, err := runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "Command error: normal error\n", r.Stderr)
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) })
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }})
r, err = runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err)
assert.Equal(t, 2, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "exit error\n", r.Stderr)
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil })
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }})
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr)
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil })
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }})
r, err = runTestApp(app, "./gitea", "test-cmd")
assert.NoError(t, err)
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
assert.Empty(t, r.Stdout)
assert.Empty(t, r.Stderr)
}
func TestCliCmdBefore(t *testing.T) {
ctxNew := context.WithValue(context.Background(), any("key"), "value")
configValues := map[string]string{}
setting.CustomConf = "/tmp/any.ini"
var actionCtx context.Context
app := newTestApp(cli.Command{
Before: func(context.Context, *cli.Command) (context.Context, error) {
configValues["before"] = setting.CustomConf
return ctxNew, nil
},
Action: func(ctx context.Context, cmd *cli.Command) error {
configValues["action"] = setting.CustomConf
actionCtx = ctx
return nil
},
})
_, err := runTestApp(app, "./gitea", "--config", "/dev/null", "test-cmd")
assert.NoError(t, err)
assert.Equal(t, ctxNew, actionCtx)
assert.Equal(t, "/tmp/any.ini", configValues["before"], "BeforeFunc must be called before preparing config")
assert.Equal(t, "/dev/null", configValues["action"])
}

View File

@@ -18,7 +18,7 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/repo"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/json"
@@ -207,7 +207,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
username := repoPathFields[0]
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
if !repo.IsValidSSHAccessRepoName(reponame) {
if !repo_model.IsValidSSHAccessRepoName(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
}
@@ -253,10 +253,12 @@ func runServ(ctx context.Context, c *cli.Command) error {
return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error)
}
// LowerCase and trim the repoPath as that's how they are stored.
// This should be done after splitting the repoPath into username and reponame
// so that username and reponame are not affected.
repoPath = strings.ToLower(results.OwnerName + "/" + results.RepoName + ".git")
// because the original repoPath maybe redirected, we need to use the returned actual repository information
if results.IsWiki {
repoPath = repo_model.RelativeWikiPath(results.OwnerName, results.RepoName)
} else {
repoPath = repo_model.RelativePath(results.OwnerName, results.RepoName)
}
// LFS SSH protocol
if verb == git.CmdVerbLfsTransfer {

2
go.mod
View File

@@ -1,6 +1,6 @@
module code.gitea.io/gitea
go 1.25.1
go 1.25.3
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
// But some CAs use negative serial number, just relax the check. related:

View File

@@ -13,6 +13,8 @@ func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
FixtureFiles: []string{
"action_runner_token.yml",
"action_run.yml",
"repository.yml",
},
})
}

View File

@@ -184,6 +184,7 @@ func (run *ActionRun) IsSchedule() bool {
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID).
NoAutoTime().
Cols("num_action_runs", "num_closed_action_runs").
SetExpr("num_action_runs",
builder.Select("count(*)").From("action_run").
Where(builder.Eq{"repo_id": repo.ID}),

View File

@@ -0,0 +1,35 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"testing"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
)
func TestUpdateRepoRunsNumbers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// update the number to a wrong one, the original is 3
_, err := db.GetEngine(t.Context()).ID(4).Cols("num_closed_action_runs").Update(&repo_model.Repository{
NumClosedActionRuns: 2,
})
assert.NoError(t, err)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.Equal(t, 4, repo.NumActionRuns)
assert.Equal(t, 2, repo.NumClosedActionRuns)
// now update will correct them, only num_actionr_runs and num_closed_action_runs should be updated
err = updateRepoRunsNumbers(t.Context(), repo)
assert.NoError(t, err)
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.Equal(t, 5, repo.NumActionRuns)
assert.Equal(t, 3, repo.NumClosedActionRuns)
}

View File

@@ -386,7 +386,7 @@ func SetNotificationStatus(ctx context.Context, notificationID int64, user *user
notification.Status = status
_, err = db.GetEngine(ctx).ID(notificationID).Update(notification)
_, err = db.GetEngine(ctx).ID(notificationID).Cols("status").Update(notification)
return notification, err
}

View File

@@ -78,7 +78,7 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st
}
key.Verified = true
if _, err := db.GetEngine(ctx).ID(key.ID).SetExpr("verified", true).Update(new(GPGKey)); err != nil {
if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil {
return "", err
}

View File

@@ -139,3 +139,24 @@
updated: 1683636626
need_approval: 0
approved_by: 0
-
id: 796
title: "update actions"
repo_id: 4
owner_id: 1
workflow_id: "artifact.yaml"
index: 191
trigger_user_id: 1
ref: "refs/heads/master"
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
event: "push"
trigger_event: "push"
is_fork_pull_request: 0
status: 5
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0

View File

@@ -129,3 +129,18 @@
status: 5
started: 1683636528
stopped: 1683636626
-
id: 205
run_id: 796
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
name: job_2
attempt: 1
job_id: job_2
task_id: 55
status: 3
started: 1683636528
stopped: 1683636626

View File

@@ -177,3 +177,24 @@
log_length: 0
log_size: 0
log_expired: 0
-
id: 55
job_id: 205
attempt: 1
runner_id: 1
status: 3 # 3 is the status code for "cancelled"
started: 1683636528
stopped: 1683636626
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4240c64a69a2cc1508825121b7b8394e48e00b1bf3718b2aaaab
token_salt: eeeeeeee
token_last_eight: eeeeeeee
log_filename: artifact-test2/2f/47.log
log_in_storage: 1
log_length: 707
log_size: 90179
log_expired: 0

View File

@@ -733,3 +733,10 @@
type: 3
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
created_unix: 946684810
-
id: 111
repo_id: 4
type: 10
config: "{}"
created_unix: 946684810

View File

@@ -110,6 +110,8 @@
num_closed_milestones: 0
num_projects: 0
num_closed_projects: 1
num_action_runs: 4
num_closed_action_runs: 3
is_private: false
is_empty: false
is_archived: false

View File

@@ -368,7 +368,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
}
// 1. update branch in database
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Cols("name").Update(&Branch{
Name: to,
}); err != nil {
return err

View File

@@ -862,10 +862,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
if err = UpdateCommentAttachments(ctx, comment, opts.Attachments); err != nil {
return err
}
case CommentTypeReopen, CommentTypeClose:
if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
return err
}
// comment type reopen and close event have their own logic to update numbers but not here
}
// update the issue's updated_unix column
return UpdateIssueCols(ctx, opts.Issue, "updated_unix")

View File

@@ -146,8 +146,19 @@ func updateIssueNumbers(ctx context.Context, issue *Issue, doer *user_model.User
}
// update repository's issue closed number
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
return nil, err
switch cmtType {
case CommentTypeClose, CommentTypeMergePull:
// only increase closed count
if err := IncrRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false); err != nil {
return nil, err
}
case CommentTypeReopen:
// only decrease closed count
if err := DecrRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false, true); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("invalid comment type: %d", cmtType)
}
return CreateComment(ctx, &CreateCommentOptions{
@@ -318,7 +329,6 @@ type NewIssueOptions struct {
Issue *Issue
LabelIDs []int64
Attachments []string // In UUID format.
IsPull bool
}
// NewIssueWithIndex creates issue with given index
@@ -369,7 +379,8 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
}
}
if err := repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.IsPull, false); err != nil {
// Update repository issue total count
if err := IncrRepoIssueNumbers(ctx, opts.Repo.ID, opts.Issue.IsPull, true); err != nil {
return err
}
@@ -439,6 +450,42 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, la
})
}
// IncrRepoIssueNumbers increments repository issue numbers.
func IncrRepoIssueNumbers(ctx context.Context, repoID int64, isPull, totalOrClosed bool) error {
dbSession := db.GetEngine(ctx)
var colName string
if totalOrClosed {
colName = util.Iif(isPull, "num_pulls", "num_issues")
} else {
colName = util.Iif(isPull, "num_closed_pulls", "num_closed_issues")
}
_, err := dbSession.Incr(colName).ID(repoID).
NoAutoCondition().NoAutoTime().
Update(new(repo_model.Repository))
return err
}
// DecrRepoIssueNumbers decrements repository issue numbers.
func DecrRepoIssueNumbers(ctx context.Context, repoID int64, isPull, includeTotal, includeClosed bool) error {
if !includeTotal && !includeClosed {
return fmt.Errorf("no numbers to decrease for repo id %d", repoID)
}
dbSession := db.GetEngine(ctx)
if includeTotal {
colName := util.Iif(isPull, "num_pulls", "num_issues")
dbSession = dbSession.Decr(colName)
}
if includeClosed {
closedColName := util.Iif(isPull, "num_closed_pulls", "num_closed_issues")
dbSession = dbSession.Decr(closedColName)
}
_, err := dbSession.ID(repoID).
NoAutoCondition().NoAutoTime().
Update(new(repo_model.Repository))
return err
}
// UpdateIssueMentions updates issue-user relations for mentioned users.
func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_model.User) error {
if len(mentions) == 0 {

View File

@@ -181,6 +181,7 @@ func updateMilestone(ctx context.Context, m *Milestone) error {
func UpdateMilestoneCounters(ctx context.Context, id int64) error {
e := db.GetEngine(ctx)
_, err := e.ID(id).
Cols("num_issues", "num_closed_issues").
SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
builder.Eq{"milestone_id": id},
)).

View File

@@ -471,13 +471,13 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Iss
issue.Index = idx
issue.Title = util.EllipsisDisplayString(issue.Title, 255)
issue.IsPull = true
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
Repo: repo,
Issue: issue,
LabelIDs: labelIDs,
Attachments: uuids,
IsPull: true,
}); err != nil {
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
return err

View File

@@ -21,6 +21,7 @@ func UpdateOpenMilestoneCounts(x *xorm.Engine) error {
for _, id := range openMilestoneIDs {
_, err := x.ID(id).
Cols("num_issues", "num_closed_issues").
SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
builder.Eq{"milestone_id": id},
)).

View File

@@ -49,6 +49,19 @@ func init() {
db.RegisterModel(new(ReviewState))
}
func (rs *ReviewState) GetViewedFileCount() int {
if len(rs.UpdatedFiles) == 0 {
return 0
}
var numViewedFiles int
for _, state := range rs.UpdatedFiles {
if state == Viewed {
numViewedFiles++
}
}
return numViewedFiles
}
// GetReviewState returns the ReviewState with all given values prefilled, whether or not it exists in the database.
// If the review didn't exist before in the database, it won't afterwards either.
// The returned boolean shows whether the review exists in the database

View File

@@ -159,7 +159,7 @@ func RemoveTopicsFromRepo(ctx context.Context, repoID int64) error {
builder.In("id",
builder.Select("topic_id").From("repo_topic").Where(builder.Eq{"repo_id": repoID}),
),
).Cols("repo_count").SetExpr("repo_count", "repo_count-1").Update(&Topic{})
).Decr("repo_count").Update(&Topic{})
if err != nil {
return err
}

View File

@@ -30,6 +30,9 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
// Chroma always uses 1-2 letters for style names, we could tolerate it at the moment
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^\w{0,2}$`)).OnElements("span")
// Line numbers on codepreview
policy.AllowAttrs("data-line-number").OnElements("span")
// Custom URL-Schemes
if len(setting.Markdown.CustomURLSchemes) > 0 {
policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)

View File

@@ -235,9 +235,6 @@ func loadServerFrom(rootCfg ConfigProvider) {
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0")
AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
}
if AcmeEmail == "" {
log.Fatal("ACME Email is not set (ACME_EMAIL).")
}
} else {
CertFile = sec.Key("CERT_FILE").String()
KeyFile = sec.Key("KEY_FILE").String()

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -412,6 +412,12 @@ func Rerun(ctx *context_module.Context) {
return
}
// rerun is not allowed if the run is not done
if !run.Status.IsDone() {
ctx.JSONError(ctx.Locale.Tr("actions.runs.not_done"))
return
}
// can not rerun job when workflow is disabled
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
cfg := cfgUnit.ActionsConfig()
@@ -420,24 +426,22 @@ func Rerun(ctx *context_module.Context) {
return
}
// reset run's start and stop time when it is done
if run.Status.IsDone() {
run.PreviousDuration = run.Duration()
run.Started = 0
run.Stopped = 0
run.Status = actions_model.StatusWaiting
if err := actions_model.UpdateRun(ctx, run, "started", "stopped", "status", "previous_duration"); err != nil {
ctx.ServerError("UpdateRun", err)
return
}
if err := run.LoadAttributes(ctx); err != nil {
ctx.ServerError("run.LoadAttributes", err)
return
}
notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run)
// reset run's start and stop time
run.PreviousDuration = run.Duration()
run.Started = 0
run.Stopped = 0
run.Status = actions_model.StatusWaiting
if err := actions_model.UpdateRun(ctx, run, "started", "stopped", "status", "previous_duration"); err != nil {
ctx.ServerError("UpdateRun", err)
return
}
if err := run.LoadAttributes(ctx); err != nil {
ctx.ServerError("run.LoadAttributes", err)
return
}
notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run)
job, jobs := getRunJobs(ctx, runIndex, jobIndex)
if ctx.Written() {
return
@@ -472,7 +476,7 @@ func Rerun(ctx *context_module.Context) {
func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shouldBlock bool) error {
status := job.Status
if !status.IsDone() || !job.Run.Status.IsDone() {
if !status.IsDone() {
return nil
}

View File

@@ -753,12 +753,16 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
// as the viewed information is designed to be loaded only on latest PR
// diff and if you're signed in.
var reviewState *pull_model.ReviewState
var numViewedFiles int
if ctx.IsSigned && isShowAllCommits {
reviewState, err = gitdiff.SyncUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diff, diffOptions)
if err != nil {
ctx.ServerError("SyncUserSpecificDiff", err)
return
}
if reviewState != nil {
numViewedFiles = reviewState.GetViewedFileCount()
}
}
diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, beforeCommitID, afterCommitID)
@@ -767,10 +771,11 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
return
}
ctx.Data["DiffShortStat"] = diffShortStat
ctx.Data["NumViewedFiles"] = numViewedFiles
ctx.PageData["prReview"] = map[string]any{
"numberOfFiles": diffShortStat.NumFiles,
"numberOfViewedFiles": diff.NumViewedFiles,
"numberOfViewedFiles": numViewedFiles,
}
if err = diff.LoadComments(ctx, issue, ctx.Doer, ctx.Data["ShowOutdatedComments"].(bool)); err != nil {

View File

@@ -236,12 +236,12 @@ func notify(ctx context.Context, input *notifyInput) error {
}
if shouldDetectSchedules {
if err := handleSchedules(ctx, schedules, commit, input, ref.String()); err != nil {
if err := handleSchedules(ctx, schedules, commit, input, ref); err != nil {
return err
}
}
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref.String())
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref)
}
func skipWorkflows(ctx context.Context, input *notifyInput, commit *git.Commit) bool {
@@ -293,7 +293,7 @@ func handleWorkflows(
detectedWorkflows []*actions_module.DetectedWorkflow,
commit *git.Commit,
input *notifyInput,
ref string,
ref git.RefName,
) error {
if len(detectedWorkflows) == 0 {
log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID)
@@ -329,7 +329,7 @@ func handleWorkflows(
WorkflowID: dwf.EntryName,
TriggerUserID: input.Doer.ID,
TriggerUser: input.Doer,
Ref: ref,
Ref: ref.String(),
CommitSHA: commit.ID.String(),
IsForkPullRequest: isForkPullRequest,
Event: input.Event,
@@ -500,13 +500,9 @@ func handleSchedules(
detectedWorkflows []*actions_module.DetectedWorkflow,
commit *git.Commit,
input *notifyInput,
ref string,
ref git.RefName,
) error {
branch, err := commit.GetBranchName()
if err != nil {
return err
}
if branch != input.Repo.DefaultBranch {
if ref.BranchName() != input.Repo.DefaultBranch {
log.Trace("commit branch is not default branch in repo")
return nil
}
@@ -552,7 +548,7 @@ func handleSchedules(
WorkflowID: dwf.EntryName,
TriggerUserID: user_model.ActionsUserID,
TriggerUser: user_model.NewActionsUser(),
Ref: ref,
Ref: ref.String(),
CommitSHA: commit.ID.String(),
Event: input.Event,
EventPayload: string(p),
@@ -614,5 +610,5 @@ func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository)
// so we use action user as the Doer of the notifyInput
notifyInput := newNotifyInputForSchedules(repo)
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, repo.DefaultBranch)
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, git.RefNameFromBranch(repo.DefaultBranch))
}

View File

@@ -7,12 +7,17 @@ import (
"context"
"fmt"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
repo_service "code.gitea.io/gitea/services/repository"
"xorm.io/builder"
)
func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bool) error {
@@ -59,6 +64,95 @@ func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bo
return nil
}
func fixUnfinishedRunStatus(ctx context.Context, logger log.Logger, autofix bool) error {
total := 0
inconsistent := 0
fixed := 0
cond := builder.In("status", []actions_model.Status{
actions_model.StatusWaiting,
actions_model.StatusRunning,
actions_model.StatusBlocked,
}).And(builder.Lt{"updated": timeutil.TimeStampNow().AddDuration(-setting.Actions.ZombieTaskTimeout)})
err := db.Iterate(
ctx,
cond,
func(ctx context.Context, run *actions_model.ActionRun) error {
total++
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
if err != nil {
return fmt.Errorf("GetRunJobsByRunID: %w", err)
}
expected := actions_model.AggregateJobStatus(jobs)
if expected == run.Status {
return nil
}
inconsistent++
logger.Warn("Run %d (repo_id=%d, index=%d) has status %s, expected %s", run.ID, run.RepoID, run.Index, run.Status, expected)
if !autofix {
return nil
}
run.Started, run.Stopped = getRunTimestampsFromJobs(run, expected, jobs)
run.Status = expected
if err := actions_model.UpdateRun(ctx, run, "status", "started", "stopped"); err != nil {
return fmt.Errorf("UpdateRun: %w", err)
}
fixed++
return nil
},
)
if err != nil {
logger.Critical("Unable to iterate unfinished runs: %v", err)
return err
}
if inconsistent == 0 {
logger.Info("Checked %d unfinished runs; all statuses are consistent.", total)
return nil
}
if autofix {
logger.Info("Checked %d unfinished runs; fixed %d of %d runs.", total, fixed, inconsistent)
} else {
logger.Warn("Checked %d unfinished runs; found %d runs need to be fixed", total, inconsistent)
}
return nil
}
func getRunTimestampsFromJobs(run *actions_model.ActionRun, newStatus actions_model.Status, jobs actions_model.ActionJobList) (started, stopped timeutil.TimeStamp) {
started = run.Started
if (newStatus.IsRunning() || newStatus.IsDone()) && started.IsZero() {
var earliest timeutil.TimeStamp
for _, job := range jobs {
if job.Started > 0 && (earliest.IsZero() || job.Started < earliest) {
earliest = job.Started
}
}
started = earliest
}
stopped = run.Stopped
if newStatus.IsDone() && stopped.IsZero() {
var latest timeutil.TimeStamp
for _, job := range jobs {
if job.Stopped > latest {
latest = job.Stopped
}
}
stopped = latest
}
return started, stopped
}
func init() {
Register(&Check{
Title: "Disable the actions unit for all mirrors",
@@ -67,4 +161,11 @@ func init() {
Run: disableMirrorActionsUnit,
Priority: 9,
})
Register(&Check{
Title: "Fix inconsistent status for unfinished actions runs",
Name: "fix-actions-unfinished-run-status",
IsDefault: false,
Run: fixUnfinishedRunStatus,
Priority: 9,
})
}

View File

@@ -0,0 +1,24 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package doctor
import (
"testing"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/log"
"github.com/stretchr/testify/assert"
)
func Test_fixUnfinishedRunStatus(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
fixUnfinishedRunStatus(t.Context(), log.GetLogger(log.DEFAULT), true)
// check if the run is cancelled by id
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 796})
assert.Equal(t, actions_model.StatusCancelled, run.Status)
}

View File

@@ -449,10 +449,9 @@ func getCommitFileLineCountAndLimitedContent(commit *git.Commit, filePath string
// Diff represents a difference between two git trees.
type Diff struct {
Start, End string
Files []*DiffFile
IsIncomplete bool
NumViewedFiles int // user-specific
Start, End string
Files []*DiffFile
IsIncomplete bool
}
// LoadComments loads comments into each line
@@ -1342,7 +1341,6 @@ outer:
// Check whether the file has already been viewed
if fileViewedState == pull_model.Viewed {
diffFile.IsViewed = true
diff.NumViewedFiles++
}
}

View File

@@ -270,16 +270,9 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) ([]string, erro
return nil, err
}
// update the total issue numbers
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false); err != nil {
if err := issues_model.DecrRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true, issue.IsClosed); err != nil {
return nil, err
}
// if the issue is closed, update the closed issue numbers
if issue.IsClosed {
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
return nil, err
}
}
if err := issues_model.UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
return nil, fmt.Errorf("error updating counters for milestone id %d: %w",

View File

@@ -128,7 +128,7 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error {
return fmt.Errorf("failed to issue MAIL command: %w", err)
}
} else {
if err = client.Mail(from); err != nil {
if err = client.Mail(fmt.Sprintf("<%s>", from)); err != nil {
return fmt.Errorf("failed to issue MAIL command: %w", err)
}
}

View File

@@ -110,6 +110,7 @@ func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePrevie
"FilePath": opts.FilePath,
"LineStart": opts.LineStart,
"LineStop": realLineStop,
"RepoName": opts.RepoName,
"RepoLink": dbRepo.Link(),
"CommitID": opts.CommitID,
"HighlightLines": highlightLines,

View File

@@ -24,15 +24,15 @@ func TestRenderHelperCodePreview(t *testing.T) {
OwnerName: "user2",
RepoName: "repo1",
CommitID: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
FilePath: "/README.md",
FilePath: "README.md",
LineStart: 1,
LineStop: 2,
})
assert.NoError(t, err)
assert.Equal(t, `<div class="code-preview-container file-content">
<div class="code-preview-header">
<a href="http://full" class="muted" rel="nofollow">/README.md</a>
repo.code_preview_line_from_to:1,2,<a href="/user2/repo1/src/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d" rel="nofollow">65f1bf27bc</a>
<a href="http://full" class="tw-font-semibold" rel="nofollow">repo1/README.md</a>
repo.code_preview_line_from_to:1,2,<a href="/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d" class="muted tw-font-mono tw-text-text" rel="nofollow">65f1bf27bc</a>
</div>
<table class="file-view">
<tbody><tr>
@@ -52,14 +52,14 @@ func TestRenderHelperCodePreview(t *testing.T) {
OwnerName: "user2",
RepoName: "repo1",
CommitID: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
FilePath: "/README.md",
FilePath: "README.md",
LineStart: 1,
})
assert.NoError(t, err)
assert.Equal(t, `<div class="code-preview-container file-content">
<div class="code-preview-header">
<a href="http://full" class="muted" rel="nofollow">/README.md</a>
repo.code_preview_line_in:1,<a href="/user2/repo1/src/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d" rel="nofollow">65f1bf27bc</a>
<a href="http://full" class="tw-font-semibold" rel="nofollow">repo1/README.md</a>
repo.code_preview_line_in:1,<a href="/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d" class="muted tw-font-mono tw-text-text" rel="nofollow">65f1bf27bc</a>
</div>
<table class="file-view">
<tbody><tr>
@@ -76,7 +76,7 @@ func TestRenderHelperCodePreview(t *testing.T) {
OwnerName: "user15",
RepoName: "big_test_private_1",
CommitID: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
FilePath: "/README.md",
FilePath: "README.md",
LineStart: 1,
LineStop: 10,
})

View File

@@ -68,6 +68,7 @@ parts:
override-build: |
set -x
sed -i 's/os.Getuid()/1/g' modules/setting/setting.go
npm install -g pnpm
TAGS="bindata sqlite sqlite_unlock_notify pam cert" make build
install -D gitea "${SNAPCRAFT_PART_INSTALL}/gitea"
cp -r options "${SNAPCRAFT_PART_INSTALL}/"

View File

@@ -1,7 +1,7 @@
<div class="code-preview-container file-content">
<div class="code-preview-header">
<a href="{{.FullURL}}" class="muted" rel="nofollow">{{.FilePath}}</a>
{{$link := HTMLFormat `<a href="%s/src/commit/%s" rel="nofollow">%s</a>` .RepoLink .CommitID (.CommitID | ShortSha) -}}
<a href="{{.FullURL}}" class="tw-font-semibold" rel="nofollow">{{.RepoName}}/{{.FilePath}}</a>
{{$link := HTMLFormat `<a href="%s/commit/%s" class="muted tw-font-mono tw-text-text" rel="nofollow">%s</a>` .RepoLink .CommitID (.CommitID | ShortSha) -}}
{{- if eq .LineStart .LineStop -}}
{{ctx.Locale.Tr "repo.code_preview_line_in" .LineStart $link}}
{{- else -}}

View File

@@ -16,7 +16,7 @@
{{else if eq .status "blocked"}}
{{svg "octicon-blocked" $size (printf "text yellow %s" $className)}}
{{else if eq .status "running"}}
{{svg "gitea-running" $size (printf "text yellow circular-spin %s" $className)}}
{{svg "gitea-running" $size (printf "text yellow rotate-clockwise %s" $className)}}
{{else}}{{/*failure, unknown*/}}
{{svg "octicon-x-circle-fill" $size (printf "text red %s" $className)}}
{{end}}

View File

@@ -27,9 +27,9 @@
{{if and .PageIsPullFiles $.SignedUserID (not .DiffNotAvailable)}}
<div class="not-mobile tw-flex tw-items-center tw-flex-col tw-whitespace-nowrap tw-mr-1">
<label for="viewed-files-summary" id="viewed-files-summary-label" data-text-changed-template="{{ctx.Locale.Tr "repo.pulls.viewed_files_label"}}">
{{ctx.Locale.Tr "repo.pulls.viewed_files_label" .Diff.NumViewedFiles .DiffShortStat.NumFiles}}
{{ctx.Locale.Tr "repo.pulls.viewed_files_label" .NumViewedFiles .DiffShortStat.NumFiles}}
</label>
<progress id="viewed-files-summary" value="{{.Diff.NumViewedFiles}}" max="{{.DiffShortStat.NumFiles}}"></progress>
<progress id="viewed-files-summary" value="{{.NumViewedFiles}}" max="{{.DiffShortStat.NumFiles}}"></progress>
</div>
{{end}}
{{template "repo/diff/whitespace_dropdown" .}}

View File

@@ -103,7 +103,7 @@
{{template "repo/issue/view_content/update_branch_by_merge" $}}
{{else if .Issue.PullRequest.IsChecking}}
<div class="item">
{{svg "octicon-sync" 16 "circular-spin"}}
{{svg "gitea-running" 16 "rotate-clockwise"}}
{{ctx.Locale.Tr "repo.pulls.is_checking"}}
</div>
{{else if .Issue.PullRequest.IsAncestor}}

View File

@@ -5,7 +5,7 @@
{{if eq .HookType "gitea"}}
{{svg "gitea-gitea" $size "img"}}
{{else if eq .HookType "gogs"}}
<img alt width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/gogs.ico">
<img alt width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/gogs.png">
{{else if eq .HookType "slack"}}
<img alt width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/slack.png">
{{else if eq .HookType "discord"}}

View File

@@ -0,0 +1,118 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"net/url"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
)
func TestActionsRerun(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session := loginUser(t, user2.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
apiRepo := createActionsTestRepo(t, token, "actions-rerun", false)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
defer doAPIDeleteRepository(httpContext)(t)
runner := newMockRunner()
runner.registerAsRepoRunner(t, repo.OwnerName, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
wfTreePath := ".gitea/workflows/actions-rerun-workflow-1.yml"
wfFileContent := `name: actions-rerun-workflow-1
on:
push:
paths:
- '.gitea/workflows/actions-rerun-workflow-1.yml'
jobs:
job1:
runs-on: ubuntu-latest
steps:
- run: echo 'job1'
job2:
runs-on: ubuntu-latest
needs: [job1]
steps:
- run: echo 'job2'
`
opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create"+wfTreePath, wfFileContent)
createWorkflowFile(t, token, user2.Name, repo.Name, wfTreePath, opts)
// fetch and exec job1
job1Task := runner.fetchTask(t)
_, _, run := getTaskAndJobAndRunByTaskID(t, job1Task.Id)
runner.execTask(t, job1Task, &mockTaskOutcome{
result: runnerv1.Result_RESULT_SUCCESS,
})
// RERUN-FAILURE: the run is not done
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.Index), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
})
session.MakeRequest(t, req, http.StatusBadRequest)
// fetch and exec job2
job2Task := runner.fetchTask(t)
runner.execTask(t, job2Task, &mockTaskOutcome{
result: runnerv1.Result_RESULT_SUCCESS,
})
// RERUN-1: rerun the run
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.Index), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
})
session.MakeRequest(t, req, http.StatusOK)
// fetch and exec job1
job1TaskR1 := runner.fetchTask(t)
runner.execTask(t, job1TaskR1, &mockTaskOutcome{
result: runnerv1.Result_RESULT_SUCCESS,
})
// fetch and exec job2
job2TaskR1 := runner.fetchTask(t)
runner.execTask(t, job2TaskR1, &mockTaskOutcome{
result: runnerv1.Result_RESULT_SUCCESS,
})
// RERUN-2: rerun job1
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.Index, 0), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
})
session.MakeRequest(t, req, http.StatusOK)
// job2 needs job1, so rerunning job1 will also rerun job2
// fetch and exec job1
job1TaskR2 := runner.fetchTask(t)
runner.execTask(t, job1TaskR2, &mockTaskOutcome{
result: runnerv1.Result_RESULT_SUCCESS,
})
// fetch and exec job2
job2TaskR2 := runner.fetchTask(t)
runner.execTask(t, job2TaskR2, &mockTaskOutcome{
result: runnerv1.Result_RESULT_SUCCESS,
})
// RERUN-3: rerun job2
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.Index, 1), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
})
session.MakeRequest(t, req, http.StatusOK)
// only job2 will rerun
// fetch and exec job2
job2TaskR3 := runner.fetchTask(t)
runner.execTask(t, job2TaskR3, &mockTaskOutcome{
result: runnerv1.Result_RESULT_SUCCESS,
})
runner.fetchNoTask(t)
})
}

View File

@@ -0,0 +1,294 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/url"
"strconv"
"strings"
"testing"
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/migration"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
mirror_service "code.gitea.io/gitea/services/mirror"
repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files"
"github.com/stretchr/testify/assert"
)
func TestScheduleUpdate(t *testing.T) {
t.Run("Push", testScheduleUpdatePush)
t.Run("PullMerge", testScheduleUpdatePullMerge)
t.Run("DisableAndEnableActionsUnit", testScheduleUpdateDisableAndEnableActionsUnit)
t.Run("ArchiveAndUnarchive", testScheduleUpdateArchiveAndUnarchive)
t.Run("MirrorSync", testScheduleUpdateMirrorSync)
}
func testScheduleUpdatePush(t *testing.T) {
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
newCron := "30 5 * * 1,3"
pushScheduleChange(t, u, repo, newCron)
branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
assert.NoError(t, err)
return branch.CommitID, newCron
})
}
func testScheduleUpdatePullMerge(t *testing.T) {
newBranchName := "feat1"
workflowTreePath := ".gitea/workflows/actions-schedule.yml"
workflowContent := `name: actions-schedule
on:
schedule:
- cron: '@every 2m' # update to 2m
jobs:
job:
runs-on: ubuntu-latest
steps:
- run: echo 'schedule workflow'
`
mergeStyles := []repo_model.MergeStyle{
repo_model.MergeStyleMerge,
repo_model.MergeStyleRebase,
repo_model.MergeStyleRebaseMerge,
repo_model.MergeStyleSquash,
repo_model.MergeStyleFastForwardOnly,
}
for _, mergeStyle := range mergeStyles {
t.Run(string(mergeStyle), func(t *testing.T) {
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
// update workflow file
_, err := files_service.ChangeRepoFiles(t.Context(), repo, user, &files_service.ChangeRepoFilesOptions{
NewBranch: newBranchName,
Files: []*files_service.ChangeRepoFile{
{
Operation: "update",
TreePath: workflowTreePath,
ContentReader: strings.NewReader(workflowContent),
},
},
Message: "update workflow schedule",
})
assert.NoError(t, err)
// create pull request
apiPull, err := doAPICreatePullRequest(testContext, repo.OwnerName, repo.Name, repo.DefaultBranch, newBranchName)(t)
assert.NoError(t, err)
// merge pull request
testPullMerge(t, testContext.Session, repo.OwnerName, repo.Name, strconv.FormatInt(apiPull.Index, 10), mergeStyle, false)
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
return pull.MergedCommitID, "@every 2m"
})
})
}
t.Run(string(repo_model.MergeStyleManuallyMerged), func(t *testing.T) {
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
// enable manual-merge
doAPIEditRepository(testContext, &api.EditRepoOption{
HasPullRequests: util.ToPointer(true),
AllowManualMerge: util.ToPointer(true),
})(t)
// update workflow file
fileResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user, &files_service.ChangeRepoFilesOptions{
NewBranch: newBranchName,
Files: []*files_service.ChangeRepoFile{
{
Operation: "update",
TreePath: workflowTreePath,
ContentReader: strings.NewReader(workflowContent),
},
},
Message: "update workflow schedule",
})
assert.NoError(t, err)
// merge and push
dstPath := t.TempDir()
u.Path = repo.FullName() + ".git"
u.User = url.UserPassword(repo.OwnerName, userPassword)
doGitClone(dstPath, u)(t)
doGitMerge(dstPath, "origin/"+newBranchName)(t)
doGitPushTestRepository(dstPath, "origin", repo.DefaultBranch)(t)
// create pull request
apiPull, err := doAPICreatePullRequest(testContext, repo.OwnerName, repo.Name, repo.DefaultBranch, newBranchName)(t)
assert.NoError(t, err)
// merge pull request manually
doAPIManuallyMergePullRequest(testContext, repo.OwnerName, repo.Name, fileResp.Commit.SHA, apiPull.Index)(t)
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
assert.Equal(t, issues_model.PullRequestStatusManuallyMerged, pull.Status)
return pull.MergedCommitID, "@every 2m"
})
})
}
func testScheduleUpdateMirrorSync(t *testing.T) {
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
// create mirror repo
opts := migration.MigrateOptions{
RepoName: "actions-schedule-mirror",
Description: "Test mirror for actions-schedule",
Private: false,
Mirror: true,
CloneAddr: repo.CloneLinkGeneral(t.Context()).HTTPS,
}
mirrorRepo, err := repo_service.CreateRepositoryDirectly(t.Context(), user, user, repo_service.CreateRepoOptions{
Name: opts.RepoName,
Description: opts.Description,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
DefaultBranch: repo.DefaultBranch,
Status: repo_model.RepositoryBeingMigrated,
}, false)
assert.NoError(t, err)
assert.True(t, mirrorRepo.IsMirror)
mirrorRepo, err = repo_service.MigrateRepositoryGitData(t.Context(), user, mirrorRepo, opts, nil)
assert.NoError(t, err)
mirrorContext := NewAPITestContext(t, user.Name, mirrorRepo.Name, auth_model.AccessTokenScopeWriteRepository)
// enable actions unit for mirror repo
assert.False(t, mirrorRepo.UnitEnabled(t.Context(), unit_model.TypeActions))
doAPIEditRepository(mirrorContext, &api.EditRepoOption{
HasActions: util.ToPointer(true),
})(t)
actionSchedule := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: mirrorRepo.ID})
scheduleSpec := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: mirrorRepo.ID, ScheduleID: actionSchedule.ID})
assert.Equal(t, "@every 1m", scheduleSpec.Spec)
// update remote repo
newCron := "30 5,17 * * 2,4"
pushScheduleChange(t, u, repo, newCron)
repoDefaultBranch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
assert.NoError(t, err)
// sync
ok := mirror_service.SyncPullMirror(t.Context(), mirrorRepo.ID)
assert.True(t, ok)
mirrorRepoDefaultBranch, err := git_model.GetBranch(t.Context(), mirrorRepo.ID, mirrorRepo.DefaultBranch)
assert.NoError(t, err)
assert.Equal(t, repoDefaultBranch.CommitID, mirrorRepoDefaultBranch.CommitID)
// check updated schedule
actionSchedule = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: mirrorRepo.ID})
scheduleSpec = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: mirrorRepo.ID, ScheduleID: actionSchedule.ID})
assert.Equal(t, newCron, scheduleSpec.Spec)
return repoDefaultBranch.CommitID, newCron
})
}
func testScheduleUpdateArchiveAndUnarchive(t *testing.T) {
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
doAPIEditRepository(testContext, &api.EditRepoOption{
Archived: util.ToPointer(true),
})(t)
assert.Zero(t, unittest.GetCount(t, &actions_model.ActionSchedule{RepoID: repo.ID}))
doAPIEditRepository(testContext, &api.EditRepoOption{
Archived: util.ToPointer(false),
})(t)
branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
assert.NoError(t, err)
return branch.CommitID, "@every 1m"
})
}
func testScheduleUpdateDisableAndEnableActionsUnit(t *testing.T) {
doTestScheduleUpdate(t, func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string) {
doAPIEditRepository(testContext, &api.EditRepoOption{
HasActions: util.ToPointer(false),
})(t)
assert.Zero(t, unittest.GetCount(t, &actions_model.ActionSchedule{RepoID: repo.ID}))
doAPIEditRepository(testContext, &api.EditRepoOption{
HasActions: util.ToPointer(true),
})(t)
branch, err := git_model.GetBranch(t.Context(), repo.ID, repo.DefaultBranch)
assert.NoError(t, err)
return branch.CommitID, "@every 1m"
})
}
type scheduleUpdateTrigger func(t *testing.T, u *url.URL, testContext APITestContext, user *user_model.User, repo *repo_model.Repository) (commitID, expectedSpec string)
func doTestScheduleUpdate(t *testing.T, updateTrigger scheduleUpdateTrigger) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session := loginUser(t, user2.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
apiRepo := createActionsTestRepo(t, token, "actions-schedule", false)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
assert.NoError(t, repo.LoadAttributes(t.Context()))
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
defer doAPIDeleteRepository(httpContext)(t)
wfTreePath := ".gitea/workflows/actions-schedule.yml"
wfFileContent := `name: actions-schedule
on:
schedule:
- cron: '@every 1m'
jobs:
job:
runs-on: ubuntu-latest
steps:
- run: echo 'schedule workflow'
`
opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wfTreePath, wfFileContent)
apiFileResp := createWorkflowFile(t, token, user2.Name, repo.Name, wfTreePath, opts1)
actionSchedule := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: repo.ID, CommitSHA: apiFileResp.Commit.SHA})
scheduleSpec := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: repo.ID, ScheduleID: actionSchedule.ID})
assert.Equal(t, "@every 1m", scheduleSpec.Spec)
commitID, expectedSpec := updateTrigger(t, u, httpContext, user2, repo)
actionSchedule = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionSchedule{RepoID: repo.ID, CommitSHA: commitID})
scheduleSpec = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: repo.ID, ScheduleID: actionSchedule.ID})
assert.Equal(t, expectedSpec, scheduleSpec.Spec)
})
}
func pushScheduleChange(t *testing.T, u *url.URL, repo *repo_model.Repository, newCron string) {
workflowTreePath := ".gitea/workflows/actions-schedule.yml"
workflowContent := `name: actions-schedule
on:
schedule:
- cron: '` + newCron + `'
jobs:
job:
runs-on: ubuntu-latest
steps:
- run: echo 'schedule workflow'
`
dstPath := t.TempDir()
u.Path = repo.FullName() + ".git"
u.User = url.UserPassword(repo.OwnerName, userPassword)
doGitClone(dstPath, u)(t)
doGitCheckoutWriteFileCommit(localGitAddCommitOptions{
LocalRepoPath: dstPath,
CheckoutBranch: repo.DefaultBranch,
TreeFilePath: workflowTreePath,
TreeFileContent: workflowContent,
})(t)
doGitPushTestRepository(dstPath, "origin", repo.DefaultBranch)(t)
}

View File

@@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/cmd"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
@@ -36,13 +37,15 @@ func Test_CmdKeys(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// FIXME: this test is not quite right. Each "command run" always re-initializes settings
defer test.MockVariableValue(&cmd.CmdKeys.Before, nil)() // don't re-initialize logger during the test
var stdout, stderr bytes.Buffer
app := &cli.Command{
Writer: &stdout,
ErrWriter: &stderr,
Commands: []*cli.Command{cmd.CmdKeys},
}
cmd.CmdKeys.HideHelp = true
err := app.Run(t.Context(), append([]string{"prog"}, tt.args...))
if tt.wantErr {
assert.Error(t, err)

View File

@@ -10,9 +10,12 @@ import (
"net/url"
"path"
"strings"
"sync"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/tests"
@@ -137,8 +140,15 @@ func TestPullCreate(t *testing.T) {
session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
assert.Equal(t, 3, repo1.NumPulls)
assert.Equal(t, 3, repo1.NumOpenPulls)
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title")
repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
assert.Equal(t, 4, repo1.NumPulls)
assert.Equal(t, 4, repo1.NumOpenPulls)
// check the redirected URL
url := test.RedirectURL(resp)
assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)
@@ -285,6 +295,44 @@ func TestPullCreatePrFromBaseToFork(t *testing.T) {
})
}
func TestPullCreateParallel(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
sessionFork := loginUser(t, "user1")
testRepoFork(t, sessionFork, "user2", "repo1", "user1", "repo1", "")
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
assert.Equal(t, 3, repo1.NumPulls)
assert.Equal(t, 3, repo1.NumOpenPulls)
var wg sync.WaitGroup
for i := range 5 {
wg.Go(func() {
branchName := fmt.Sprintf("new-branch-%d", i)
testEditFileToNewBranch(t, sessionFork, "user1", "repo1", "master", branchName, "README.md", fmt.Sprintf("Hello, World (Edited) %d\n", i))
// Create a PR
resp := testPullCreateDirectly(t, sessionFork, createPullRequestOptions{
BaseRepoOwner: "user2",
BaseRepoName: "repo1",
BaseBranch: "master",
HeadRepoOwner: "user1",
HeadRepoName: "repo1",
HeadBranch: branchName,
Title: fmt.Sprintf("This is a pull title %d", i),
})
// check the redirected URL
url := test.RedirectURL(resp)
assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)
})
}
wg.Wait()
repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
assert.Equal(t, 8, repo1.NumPulls)
assert.Equal(t, 8, repo1.NumOpenPulls)
})
}
func TestCreateAgitPullWithReadPermission(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
dstPath := t.TempDir()
@@ -300,8 +348,16 @@ func TestCreateAgitPullWithReadPermission(t *testing.T) {
TreeFileContent: "temp content",
})(t)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
assert.Equal(t, 3, repo.NumPulls)
assert.Equal(t, 3, repo.NumOpenPulls)
err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+"test-topic").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
assert.NoError(t, err)
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
assert.Equal(t, 4, repo.NumPulls)
assert.Equal(t, 4, repo.NumOpenPulls)
})
}

View File

@@ -99,12 +99,24 @@ func TestPullMerge(t *testing.T) {
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
assert.Equal(t, 3, repo.NumPulls)
assert.Equal(t, 3, repo.NumOpenPulls)
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title")
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
assert.Equal(t, 4, repo.NumPulls)
assert.Equal(t, 4, repo.NumOpenPulls)
elem := strings.Split(test.RedirectURL(resp), "/")
assert.Equal(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false)
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
assert.Equal(t, 4, repo.NumPulls)
assert.Equal(t, 3, repo.NumOpenPulls)
hookTasks, err = webhook.HookTasks(t.Context(), 1, 1)
assert.NoError(t, err)
assert.Len(t, hookTasks, hookTasksLenBefore+1)
@@ -121,12 +133,24 @@ func TestPullRebase(t *testing.T) {
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
assert.Equal(t, 3, repo.NumPulls)
assert.Equal(t, 3, repo.NumOpenPulls)
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title")
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
assert.Equal(t, 4, repo.NumPulls)
assert.Equal(t, 4, repo.NumOpenPulls)
elem := strings.Split(test.RedirectURL(resp), "/")
assert.Equal(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebase, false)
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
assert.Equal(t, 4, repo.NumPulls)
assert.Equal(t, 3, repo.NumOpenPulls)
hookTasks, err = webhook.HookTasks(t.Context(), 1, 1)
assert.NoError(t, err)
assert.Len(t, hookTasks, hookTasksLenBefore+1)
@@ -143,12 +167,24 @@ func TestPullRebaseMerge(t *testing.T) {
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
assert.Equal(t, 3, repo.NumPulls)
assert.Equal(t, 3, repo.NumOpenPulls)
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title")
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
assert.Equal(t, 4, repo.NumPulls)
assert.Equal(t, 4, repo.NumOpenPulls)
elem := strings.Split(test.RedirectURL(resp), "/")
assert.Equal(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebaseMerge, false)
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
assert.Equal(t, 4, repo.NumPulls)
assert.Equal(t, 3, repo.NumOpenPulls)
hookTasks, err = webhook.HookTasks(t.Context(), 1, 1)
assert.NoError(t, err)
assert.Len(t, hookTasks, hookTasksLenBefore+1)
@@ -184,12 +220,24 @@ func TestPullCleanUpAfterMerge(t *testing.T) {
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited - TestPullCleanUpAfterMerge)\n")
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
assert.Equal(t, 3, repo.NumPulls)
assert.Equal(t, 3, repo.NumOpenPulls)
resp := testPullCreate(t, session, "user1", "repo1", false, "master", "feature/test", "This is a pull title")
elem := strings.Split(test.RedirectURL(resp), "/")
assert.Equal(t, "pulls", elem[3])
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
assert.Equal(t, 4, repo.NumPulls)
assert.Equal(t, 4, repo.NumOpenPulls)
testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false)
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
assert.Equal(t, 4, repo.NumPulls)
assert.Equal(t, 3, repo.NumOpenPulls)
// Check PR branch deletion
resp = testPullCleanUp(t, session, elem[1], elem[2], elem[4])
respJSON := struct {

View File

@@ -11,7 +11,9 @@ import (
"strings"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/tests"
@@ -71,3 +73,46 @@ func Test_RepoWikiPages(t *testing.T) {
assert.Equal(t, expectedPagePaths[i], pagePath)
})
}
func Test_WikiClone(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
username := "user2"
reponame := "repo1"
wikiPath := username + "/" + reponame + ".wiki.git"
keyname := "my-testing-key"
baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
u.Path = wikiPath
t.Run("Clone HTTP", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
dstLocalPath := t.TempDir()
assert.NoError(t, git.Clone(t.Context(), u.String(), dstLocalPath, git.CloneRepoOptions{}))
content, err := os.ReadFile(filepath.Join(dstLocalPath, "Home.md"))
assert.NoError(t, err)
assert.Equal(t, "# Home page\n\nThis is the home page!\n", string(content))
})
t.Run("Clone SSH", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
dstLocalPath := t.TempDir()
sshURL := createSSHUrl(wikiPath, u)
withKeyFile(t, keyname, func(keyFile string) {
var keyID int64
t.Run("CreateUserKey", doAPICreateUserKey(baseAPITestContext, "test-key", keyFile, func(t *testing.T, key api.PublicKey) {
keyID = key.ID
}))
assert.NotZero(t, keyID)
// Setup clone folder
assert.NoError(t, git.Clone(t.Context(), sshURL.String(), dstLocalPath, git.CloneRepoOptions{}))
content, err := os.ReadFile(filepath.Join(dstLocalPath, "Home.md"))
assert.NoError(t, err)
assert.Equal(t, "# Home page\n\nThis is the home page!\n", string(content))
})
})
})
}

View File

@@ -5,6 +5,7 @@
}
.markup .code-preview-container .code-preview-header {
color: var(--color-text-light-1);
border-bottom: 1px solid var(--color-secondary);
padding: 0.5em;
font-size: 12px;

View File

@@ -116,14 +116,12 @@ code.language-math.is-loading::after {
animation-timing-function: ease-in-out;
}
/* FIXME: `octicon-sync` is counterclockwise, so this animation is also counterclockwise, it looks somewhat strange.
Ideally in the future we should use a better image for clockwise animation. */
.circular-spin {
animation: circular-spin-keyframes 1s linear infinite;
.rotate-clockwise {
animation: rotate-clockwise-keyframes 1s linear infinite;
}
@keyframes circular-spin-keyframes {
@keyframes rotate-clockwise-keyframes {
100% {
transform: rotate(-360deg);
transform: rotate(360deg);
}
}

View File

@@ -24,7 +24,7 @@ withDefaults(defineProps<{
<SvgIcon name="octicon-stop" class="text grey" :size="size" :class="className" v-else-if="status === 'cancelled'"/>
<SvgIcon name="octicon-circle" class="text grey" :size="size" :class="className" v-else-if="status === 'waiting'"/>
<SvgIcon name="octicon-blocked" class="text yellow" :size="size" :class="className" v-else-if="status === 'blocked'"/>
<SvgIcon name="gitea-running" class="text yellow" :size="size" :class="'circular-spin ' + className" v-else-if="status === 'running'"/>
<SvgIcon name="gitea-running" class="text yellow" :size="size" :class="'rotate-clockwise ' + className" v-else-if="status === 'running'"/>
<SvgIcon name="octicon-x-circle-fill" class="text red" :size="size" v-else/><!-- failure, unknown -->
</span>
</template>

View File

@@ -608,7 +608,7 @@ export default defineComponent({
<!-- If the job is done and the job step log is loaded for the first time, show the loading icon
currentJobStepsStates[i].cursor === null means the log is loaded for the first time
-->
<SvgIcon v-if="isDone(run.status) && currentJobStepsStates[i].expanded && currentJobStepsStates[i].cursor === null" name="octicon-sync" class="tw-mr-2 circular-spin"/>
<SvgIcon v-if="isDone(run.status) && currentJobStepsStates[i].expanded && currentJobStepsStates[i].cursor === null" name="gitea-running" class="tw-mr-2 rotate-clockwise"/>
<SvgIcon v-else :name="currentJobStepsStates[i].expanded ? 'octicon-chevron-down': 'octicon-chevron-right'" :class="['tw-mr-2', !isExpandable(jobStep.status) && 'tw-invisible']"/>
<ActionRunStatus :status="jobStep.status" class="tw-mr-2"/>

View File

@@ -150,7 +150,7 @@ const options: ChartOptions<'line'> = {
<div class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="tw-m-auto">
<div v-if="isLoading">
<SvgIcon name="octicon-sync" class="tw-mr-2 circular-spin"/>
<SvgIcon name="gitea-running" class="tw-mr-2 rotate-clockwise"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">

View File

@@ -381,7 +381,7 @@ export default defineComponent({
<div class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="tw-m-auto">
<div v-if="isLoading">
<SvgIcon name="octicon-sync" class="tw-mr-2 circular-spin"/>
<SvgIcon name="gitea-running" class="tw-mr-2 rotate-clockwise"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">

View File

@@ -128,7 +128,7 @@ const options: ChartOptions<'bar'> = {
<div class="tw-flex ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="tw-m-auto">
<div v-if="isLoading">
<SvgIcon name="octicon-sync" class="tw-mr-2 circular-spin"/>
<SvgIcon name="gitea-running" class="tw-mr-2 rotate-clockwise"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">

View File

@@ -62,7 +62,7 @@ const onItemClick = (e: MouseEvent) => {
@click.stop="onItemClick"
>
<div v-if="item.entryMode === 'tree'" class="item-toggle">
<SvgIcon v-if="isLoading" name="octicon-sync" class="circular-spin"/>
<SvgIcon v-if="isLoading" name="gitea-running" class="rotate-clockwise"/>
<SvgIcon v-else :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'" @click.stop.prevent="doLoadChildren"/>
</div>
<div class="item-content">

View File

@@ -20,14 +20,6 @@ function refreshViewedFilesSummary() {
.replace('%[2]d', prReview.numberOfFiles);
}
// Explicitly recounts how many files the user has currently reviewed by counting the number of checked "viewed" checkboxes
// Additionally, the viewed files summary will be updated if it exists
export function countAndUpdateViewedFiles() {
// The number of files is constant, but the number of viewed files can change because files can be loaded dynamically
prReview.numberOfViewedFiles = document.querySelectorAll(`${viewedCheckboxSelector} > input[type=checkbox][checked]`).length;
refreshViewedFilesSummary();
}
// Initializes a listener for all children of the given html element
// (for example 'document' in the most basic case)
// to watch for changes of viewed-file checkboxes

View File

@@ -2,7 +2,7 @@ import {initRepoIssueContentHistory} from './repo-issue-content.ts';
import {initDiffFileTree} from './repo-diff-filetree.ts';
import {initDiffCommitSelect} from './repo-diff-commitselect.ts';
import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.ts';
import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.ts';
import {initViewedCheckboxListenerFor, initExpandAndCollapseFilesButton} from './pull-view-file.ts';
import {initImageDiff} from './imagediff.ts';
import {showErrorToast} from '../modules/toast.ts';
import {submitEventSubmitter, queryElemSiblings, hideElem, showElem, animateOnce, addDelegatedEventListener, createElementFromHTML, queryElems} from '../utils/dom.ts';
@@ -152,7 +152,6 @@ function onShowMoreFiles() {
// TODO: replace these calls with the "observer.ts" methods
initRepoIssueContentHistory();
initViewedCheckboxListenerFor();
countAndUpdateViewedFiles();
initImageDiff();
initDiffHeaderPopup();
}