mirror of
https://github.com/go-gitea/gitea.git
synced 2026-03-29 18:38:27 +02:00
Merge branch 'main' into feature/workflow-graph
This commit is contained in:
commit
6e6decd936
2
.gitignore
vendored
2
.gitignore
vendored
@ -121,8 +121,6 @@ prime/
|
||||
/.goosehints
|
||||
/.windsurfrules
|
||||
/.github/copilot-instructions.md
|
||||
/AGENT.md
|
||||
/CLAUDE.md
|
||||
/llms.txt
|
||||
|
||||
# Ignore worktrees when working on multiple branches
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
# Instructions for agents
|
||||
|
||||
- Use `make help` to find available development targets
|
||||
- Use the latest Golang stable release when working on Go code
|
||||
- Use the latest Node.js LTS release when working on TypeScript code
|
||||
- Before committing `.go` changes, run `make fmt` to format, and run `make lint-go` to lint
|
||||
- Before committing `.ts` changes, run `make lint-js` to lint
|
||||
- Before committing `go.mod` changes, run `make tidy`
|
||||
|
||||
2
go.mod
2
go.mod
@ -2,7 +2,7 @@ module code.gitea.io/gitea
|
||||
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.25.6
|
||||
toolchain go1.25.7
|
||||
|
||||
// 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:
|
||||
|
||||
@ -658,12 +658,18 @@ func (pr *PullRequest) IsWorkInProgress(ctx context.Context) bool {
|
||||
|
||||
// HasWorkInProgressPrefix determines if the given PR title has a Work In Progress prefix
|
||||
func HasWorkInProgressPrefix(title string) bool {
|
||||
_, ok := CutWorkInProgressPrefix(title)
|
||||
return ok
|
||||
}
|
||||
|
||||
func CutWorkInProgressPrefix(title string) (origTitle string, ok bool) {
|
||||
for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
|
||||
if strings.HasPrefix(strings.ToUpper(title), strings.ToUpper(prefix)) {
|
||||
return true
|
||||
prefixLen := len(prefix)
|
||||
if prefixLen <= len(title) && util.AsciiEqualFold(title[:prefixLen], prefix) {
|
||||
return title[len(prefix):], true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return title, false
|
||||
}
|
||||
|
||||
// IsFilesConflicted determines if the Pull Request has changes conflicting with the target branch.
|
||||
|
||||
48
modules/templates/util_render_comment.go
Normal file
48
modules/templates/util_render_comment.go
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package templates
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"strings"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/modules/htmlutil"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func commentTimelineEventIsWipToggle(c *issues_model.Comment) (isToggle, isWip bool) {
|
||||
title1, ok1 := issues_model.CutWorkInProgressPrefix(c.OldTitle)
|
||||
title2, ok2 := issues_model.CutWorkInProgressPrefix(c.NewTitle)
|
||||
return ok1 != ok2 && strings.TrimSpace(title1) == strings.TrimSpace(title2), ok2
|
||||
}
|
||||
|
||||
func (ut *RenderUtils) RenderTimelineEventBadge(c *issues_model.Comment) template.HTML {
|
||||
if c.Type == issues_model.CommentTypeChangeTitle {
|
||||
isToggle, isWip := commentTimelineEventIsWipToggle(c)
|
||||
if !isToggle {
|
||||
return svg.RenderHTML("octicon-pencil")
|
||||
}
|
||||
return util.Iif(isWip, svg.RenderHTML("octicon-git-pull-request-draft"), svg.RenderHTML("octicon-eye"))
|
||||
}
|
||||
setting.PanicInDevOrTesting("unimplemented comment type %v: %v", c.Type, c)
|
||||
return htmlutil.HTMLFormat("(CommentType:%v)", c.Type)
|
||||
}
|
||||
|
||||
func (ut *RenderUtils) RenderTimelineEventComment(c *issues_model.Comment, createdStr template.HTML) template.HTML {
|
||||
if c.Type == issues_model.CommentTypeChangeTitle {
|
||||
locale := ut.ctx.Value(translation.ContextKey).(translation.Locale)
|
||||
isToggle, isWip := commentTimelineEventIsWipToggle(c)
|
||||
if !isToggle {
|
||||
return locale.Tr("repo.issues.change_title_at", ut.RenderEmoji(c.OldTitle), ut.RenderEmoji(c.NewTitle), createdStr)
|
||||
}
|
||||
trKey := util.Iif(isWip, "repo.pulls.marked_as_work_in_progress_at", "repo.pulls.marked_as_ready_for_review_at")
|
||||
return locale.Tr(trKey, createdStr)
|
||||
}
|
||||
setting.PanicInDevOrTesting("unimplemented comment type %v: %v", c.Type, c)
|
||||
return htmlutil.HTMLFormat("(Comment:%v,%v)", c.Type, c.Content)
|
||||
}
|
||||
31
modules/templates/util_render_comment_test.go
Normal file
31
modules/templates/util_render_comment_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package templates
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"testing"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRenderTimelineEventComment(t *testing.T) {
|
||||
ctx := reqctx.NewRequestContextForTest(t.Context())
|
||||
ctx.SetContextValue(translation.ContextKey, &translation.MockLocale{})
|
||||
ut := &RenderUtils{ctx: ctx}
|
||||
var createdStr template.HTML = "(created-at)"
|
||||
|
||||
c := &issues_model.Comment{Type: issues_model.CommentTypeChangeTitle, OldTitle: "WIP: title", NewTitle: "title"}
|
||||
assert.Equal(t, "repo.pulls.marked_as_ready_for_review_at:(created-at)", string(ut.RenderTimelineEventComment(c, createdStr)))
|
||||
|
||||
c = &issues_model.Comment{Type: issues_model.CommentTypeChangeTitle, OldTitle: "title", NewTitle: "WIP: title"}
|
||||
assert.Equal(t, "repo.pulls.marked_as_work_in_progress_at:(created-at)", string(ut.RenderTimelineEventComment(c, createdStr)))
|
||||
|
||||
c = &issues_model.Comment{Type: issues_model.CommentTypeChangeTitle, OldTitle: "title", NewTitle: "WIP: new title"}
|
||||
assert.Equal(t, "repo.issues.change_title_at:title,WIP: new title,(created-at)", string(ut.RenderTimelineEventComment(c, createdStr)))
|
||||
}
|
||||
@ -1778,6 +1778,8 @@
|
||||
"repo.pulls.title_desc": "wants to merge %[1]d commits from <code>%[2]s</code> into <code id=\"branch_target\">%[3]s</code>",
|
||||
"repo.pulls.merged_title_desc": "merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s",
|
||||
"repo.pulls.change_target_branch_at": "changed target branch from <b>%s</b> to <b>%s</b> %s",
|
||||
"repo.pulls.marked_as_work_in_progress_at": "marked the pull request as work in progress %s",
|
||||
"repo.pulls.marked_as_ready_for_review_at": "marked the pull request as ready for review %s",
|
||||
"repo.pulls.tab_conversation": "Conversation",
|
||||
"repo.pulls.tab_commits": "Commits",
|
||||
"repo.pulls.tab_files": "Files Changed",
|
||||
|
||||
@ -344,6 +344,35 @@ func (d *pullCommitStatusCheckData) CommitStatusCheckPrompt(locale translation.L
|
||||
return locale.TrString("repo.pulls.status_checking")
|
||||
}
|
||||
|
||||
func getViewPullHeadBranchInfo(ctx *context.Context, pull *issues_model.PullRequest, baseGitRepo *git.Repository) (headCommitID string, headCommitExists bool, err error) {
|
||||
if pull.HeadRepo == nil {
|
||||
return "", false, nil
|
||||
}
|
||||
headGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pull.HeadRepo)
|
||||
if err != nil {
|
||||
return "", false, util.Iif(errors.Is(err, util.ErrNotExist), nil, err)
|
||||
}
|
||||
defer closer.Close()
|
||||
|
||||
if pull.Flow == issues_model.PullRequestFlowGithub {
|
||||
headCommitExists, _ = git_model.IsBranchExist(ctx, pull.HeadRepo.ID, pull.HeadBranch)
|
||||
} else {
|
||||
headCommitExists = gitrepo.IsReferenceExist(ctx, pull.BaseRepo, pull.GetGitHeadRefName())
|
||||
}
|
||||
|
||||
if headCommitExists {
|
||||
if pull.Flow != issues_model.PullRequestFlowGithub {
|
||||
headCommitID, err = baseGitRepo.GetRefCommitID(pull.GetGitHeadRefName())
|
||||
} else {
|
||||
headCommitID, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
|
||||
}
|
||||
if err != nil {
|
||||
return "", false, util.Iif(errors.Is(err, util.ErrNotExist), nil, err)
|
||||
}
|
||||
}
|
||||
return headCommitID, headCommitExists, nil
|
||||
}
|
||||
|
||||
// prepareViewPullInfo show meta information for a pull request preview page
|
||||
func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_service.CompareInfo {
|
||||
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
||||
@ -430,34 +459,10 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s
|
||||
return compareInfo
|
||||
}
|
||||
|
||||
var headBranchExist bool
|
||||
var headBranchSha string
|
||||
// HeadRepo may be missing
|
||||
if pull.HeadRepo != nil {
|
||||
headGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pull.HeadRepo)
|
||||
if err != nil {
|
||||
ctx.ServerError("RepositoryFromContextOrOpen", err)
|
||||
return nil
|
||||
}
|
||||
defer closer.Close()
|
||||
|
||||
if pull.Flow == issues_model.PullRequestFlowGithub {
|
||||
headBranchExist, _ = git_model.IsBranchExist(ctx, pull.HeadRepo.ID, pull.HeadBranch)
|
||||
} else {
|
||||
headBranchExist = gitrepo.IsReferenceExist(ctx, pull.BaseRepo, pull.GetGitHeadRefName())
|
||||
}
|
||||
|
||||
if headBranchExist {
|
||||
if pull.Flow != issues_model.PullRequestFlowGithub {
|
||||
headBranchSha, err = baseGitRepo.GetRefCommitID(pull.GetGitHeadRefName())
|
||||
} else {
|
||||
headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
|
||||
}
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranchCommitID", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
headBranchSha, headBranchExist, err := getViewPullHeadBranchInfo(ctx, pull, baseGitRepo)
|
||||
if err != nil {
|
||||
ctx.ServerError("getViewPullHeadBranchInfo", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if headBranchExist {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{{template "base/alert"}}
|
||||
{{range .Issue.Comments}}
|
||||
{{range $comment := .Issue.Comments}}
|
||||
{{if call $.ShouldShowCommentType .Type}}
|
||||
{{$createdStr:= DateUtils.TimeSince .CreatedUnix}}
|
||||
|
||||
@ -220,11 +220,11 @@
|
||||
</div>
|
||||
{{else if eq .Type 10}}
|
||||
<div class="timeline-item event" id="{{.HashTag}}">
|
||||
<span class="badge">{{svg "octicon-pencil"}}</span>
|
||||
<span class="badge">{{ctx.RenderUtils.RenderTimelineEventBadge $comment}}</span>
|
||||
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
||||
<span class="comment-text-line">
|
||||
{{template "shared/user/authorlink" .Poster}}
|
||||
{{ctx.Locale.Tr "repo.issues.change_title_at" (.OldTitle|ctx.RenderUtils.RenderEmoji) (.NewTitle|ctx.RenderUtils.RenderEmoji) $createdStr}}
|
||||
{{ctx.RenderUtils.RenderTimelineEventComment $comment $createdStr}}
|
||||
</span>
|
||||
</div>
|
||||
{{else if eq .Type 11}}
|
||||
|
||||
22
web_src/js/components/RepoActionView.test.ts
Normal file
22
web_src/js/components/RepoActionView.test.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import {shouldHideLine, type LogLine} from './RepoActionView.vue';
|
||||
|
||||
test('shouldHideLine', () => {
|
||||
expect(([
|
||||
{index: 1, message: 'Starting build process', timestamp: 1000},
|
||||
{index: 2, message: '::add-matcher::/home/runner/go/pkg/mod/example.com/tool/matcher.json', timestamp: 1001},
|
||||
{index: 3, message: 'Running tests...', timestamp: 1002},
|
||||
{index: 4, message: '##[add-matcher]/opt/hostedtoolcache/go/1.25.7/x64/matchers.json', timestamp: 1003},
|
||||
{index: 5, message: 'Test suite started', timestamp: 1004},
|
||||
{index: 7, message: 'All tests passed', timestamp: 1006},
|
||||
{index: 8, message: '::remove-matcher owner=go::', timestamp: 1007},
|
||||
{index: 9, message: 'Build complete', timestamp: 1008},
|
||||
] as Array<LogLine>).filter((line) => !shouldHideLine(line)).map((line) => line.message)).toMatchInlineSnapshot(`
|
||||
[
|
||||
"Starting build process",
|
||||
"Running tests...",
|
||||
"Test suite started",
|
||||
"All tests passed",
|
||||
"Build complete",
|
||||
]
|
||||
`);
|
||||
});
|
||||
@ -16,14 +16,19 @@ type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'ca
|
||||
|
||||
type StepContainerElement = HTMLElement & {_stepLogsActiveContainer?: HTMLElement}
|
||||
|
||||
type LogLine = {
|
||||
export type LogLine = {
|
||||
index: number;
|
||||
timestamp: number;
|
||||
message: string;
|
||||
};
|
||||
|
||||
// `##[group]` is from Azure Pipelines, just supported by the way. https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands
|
||||
const LogLinePrefixesGroup = ['::group::', '##[group]'];
|
||||
const LogLinePrefixesEndGroup = ['::endgroup::', '##[endgroup]'];
|
||||
// https://github.com/actions/toolkit/blob/master/docs/commands.md
|
||||
// https://github.com/actions/runner/blob/main/docs/adrs/0276-problem-matchers.md#registration
|
||||
// Although there should be no `##[add-matcher]` syntax, there are still such outputs when using act-runner
|
||||
const LogLinePrefixesHidden = ['::add-matcher::', '##[add-matcher]', '::remove-matcher'];
|
||||
|
||||
type LogLineCommand = {
|
||||
name: 'group' | 'endgroup',
|
||||
@ -66,6 +71,15 @@ function parseLineCommand(line: LogLine): LogLineCommand | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function shouldHideLine(line: LogLine): boolean {
|
||||
for (const prefix of LogLinePrefixesHidden) {
|
||||
if (line.message.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isLogElementInViewport(el: Element, {extraViewPortHeight}={extraViewPortHeight: 0}): boolean {
|
||||
const rect = el.getBoundingClientRect();
|
||||
// only check whether bottom is in viewport, because the log element can be a log group which is usually tall
|
||||
@ -320,6 +334,7 @@ export default defineComponent({
|
||||
|
||||
appendLogs(stepIndex: number, startTime: number, logLines: LogLine[]) {
|
||||
for (const line of logLines) {
|
||||
if (shouldHideLine(line)) continue;
|
||||
const el = this.getActiveLogsContainer(stepIndex);
|
||||
const cmd = parseLineCommand(line);
|
||||
if (cmd?.name === 'group') {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user