mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-16 06:07:19 +02:00
Backport #37189 by @bircni If a workflow is not in default branch the hooks could not be detected Fixes #37169 Co-authored-by: Nicolas <bircni@icloud.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9b9d1e31aa
commit
ba5117e4e4
@ -5,6 +5,7 @@ package actions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
@ -20,6 +21,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/repository"
|
"code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
notify_service "code.gitea.io/gitea/services/notify"
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
@ -805,7 +807,10 @@ func (n *actionsNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *rep
|
|||||||
}
|
}
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
convertedWorkflow, err := convert.GetActionWorkflow(ctx, gitRepo, repo, run.WorkflowID)
|
convertedWorkflow, err := convert.GetActionWorkflowByRef(ctx, gitRepo, repo, run.WorkflowID, git.RefName(run.Ref))
|
||||||
|
if err != nil && errors.Is(err, util.ErrNotExist) {
|
||||||
|
convertedWorkflow, err = convert.GetActionWorkflow(ctx, gitRepo, repo, run.WorkflowID)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetActionWorkflow: %v", err)
|
log.Error("GetActionWorkflow: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
109
services/convert/action_test.go
Normal file
109
services/convert/action_test.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// buildWorkflowTestRepo creates a temporary git repository for testing GetActionWorkflow.
|
||||||
|
// The default branch "main" has no workflow files; "feature" and "release-v1" each add their own workflow file.
|
||||||
|
func buildWorkflowTestRepo(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
ctx := t.Context()
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
_, _, err := gitcmd.NewCommand("init").WithDir(tmpDir).RunStdString(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
readme := "readme"
|
||||||
|
featureWF := "on: [push]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: echo test\n"
|
||||||
|
releaseWF := "on: [push]\njobs:\n release:\n runs-on: ubuntu-latest\n steps:\n - run: echo release\n"
|
||||||
|
|
||||||
|
// Build a git fast-import stream:
|
||||||
|
// :4 = initial commit on main (README.md only)
|
||||||
|
// :5 = feature branch commit (adds feature workflow)
|
||||||
|
// :6 = release commit from :4 (adds release workflow, tagged release-v1, not on main)
|
||||||
|
var sb strings.Builder
|
||||||
|
fmt.Fprintf(&sb, "blob\nmark :1\ndata %d\n%s\n", len(readme), readme)
|
||||||
|
fmt.Fprintf(&sb, "blob\nmark :2\ndata %d\n%s\n", len(featureWF), featureWF)
|
||||||
|
fmt.Fprintf(&sb, "blob\nmark :3\ndata %d\n%s\n", len(releaseWF), releaseWF)
|
||||||
|
fmt.Fprintf(&sb, "commit refs/heads/main\nmark :4\nauthor Test <test@gitea.com> 1000000000 +0000\ncommitter Test <test@gitea.com> 1000000000 +0000\ndata 14\ninitial commit\nM 100644 :1 README.md\n\n")
|
||||||
|
fmt.Fprintf(&sb, "commit refs/heads/feature\nmark :5\nauthor Test <test@gitea.com> 1000000001 +0000\ncommitter Test <test@gitea.com> 1000000001 +0000\ndata 12\nadd workflow\nfrom :4\nM 100644 :2 .gitea/workflows/my-workflow.yml\n\n")
|
||||||
|
fmt.Fprintf(&sb, "reset refs/pull/42/merge\nfrom :5\n\n")
|
||||||
|
fmt.Fprintf(&sb, "commit refs/heads/main\nmark :6\nauthor Test <test@gitea.com> 1000000002 +0000\ncommitter Test <test@gitea.com> 1000000002 +0000\ndata 16\nrelease workflow\nfrom :4\nM 100644 :3 .gitea/workflows/my-workflow.yml\n\n")
|
||||||
|
fmt.Fprintf(&sb, "reset refs/tags/release-v1\nfrom :6\n\n")
|
||||||
|
fmt.Fprintf(&sb, "reset refs/heads/main\nfrom :4\n\n")
|
||||||
|
fmt.Fprintf(&sb, "done\n")
|
||||||
|
|
||||||
|
_, _, err = gitcmd.NewCommand("fast-import").WithDir(tmpDir).WithStdinBytes([]byte(sb.String())).RunStdString(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return tmpDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetActionWorkflow_FallbackRef(t *testing.T) {
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
repoDir := buildWorkflowTestRepo(t)
|
||||||
|
|
||||||
|
gitRepo, err := git.OpenRepository(ctx, repoDir)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
repo := &repo_model.Repository{
|
||||||
|
DefaultBranch: "main",
|
||||||
|
OwnerName: "test-owner",
|
||||||
|
Name: "test-repo",
|
||||||
|
Units: []*repo_model.RepoUnit{
|
||||||
|
{
|
||||||
|
Type: unit.TypeActions,
|
||||||
|
Config: &repo_model.ActionsConfig{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("returns error when workflow only on non-default branch", func(t *testing.T) {
|
||||||
|
_, err := GetActionWorkflow(ctx, gitRepo, repo, "my-workflow.yml")
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns workflow when found via ref", func(t *testing.T) {
|
||||||
|
wf, err := GetActionWorkflowByRef(ctx, gitRepo, repo, "my-workflow.yml", git.RefName("refs/heads/feature"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "my-workflow.yml", wf.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns workflow when found via pull ref", func(t *testing.T) {
|
||||||
|
wf, err := GetActionWorkflowByRef(ctx, gitRepo, repo, "my-workflow.yml", git.RefName("refs/pull/42/merge"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "my-workflow.yml", wf.ID)
|
||||||
|
assert.Contains(t, wf.HTMLURL, "/src/commit/")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns workflow with tag link when found via tag ref", func(t *testing.T) {
|
||||||
|
wf, err := GetActionWorkflowByRef(ctx, gitRepo, repo, "my-workflow.yml", git.RefName("refs/tags/release-v1"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "my-workflow.yml", wf.ID)
|
||||||
|
assert.Contains(t, wf.HTMLURL, "/src/tag/release-v1/")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns error when workflow missing from ref", func(t *testing.T) {
|
||||||
|
_, err := GetActionWorkflowByRef(ctx, gitRepo, repo, "nonexistent.yml", git.RefName("refs/heads/feature"))
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -387,12 +387,15 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getActionWorkflowEntry(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, branchName, folder string, entry *git.TreeEntry) *api.ActionWorkflow {
|
func getActionWorkflowEntry(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, refName git.RefName, folder string, entry *git.TreeEntry) *api.ActionWorkflow {
|
||||||
cfgUnit := repo.MustGetUnit(ctx, unit.TypeActions)
|
cfgUnit := repo.MustGetUnit(ctx, unit.TypeActions)
|
||||||
cfg := cfgUnit.ActionsConfig()
|
cfg := cfgUnit.ActionsConfig()
|
||||||
|
|
||||||
workflowURL := fmt.Sprintf("%s/actions/workflows/%s", repo.APIURL(), util.PathEscapeSegments(entry.Name()))
|
workflowURL := fmt.Sprintf("%s/actions/workflows/%s", repo.APIURL(), util.PathEscapeSegments(entry.Name()))
|
||||||
workflowRepoURL := fmt.Sprintf("%s/src/branch/%s/%s/%s", repo.HTMLURL(ctx), util.PathEscapeSegments(branchName), util.PathEscapeSegments(folder), util.PathEscapeSegments(entry.Name()))
|
workflowRepoURL := fmt.Sprintf("%s/src/commit/%s/%s/%s", repo.HTMLURL(ctx), commit.ID.String(), util.PathEscapeSegments(folder), util.PathEscapeSegments(entry.Name()))
|
||||||
|
if refWebLinkPath := refName.RefWebLinkPath(); refWebLinkPath != "" {
|
||||||
|
workflowRepoURL = fmt.Sprintf("%s/src/%s/%s/%s", repo.HTMLURL(ctx), refWebLinkPath, util.PathEscapeSegments(folder), util.PathEscapeSegments(entry.Name()))
|
||||||
|
}
|
||||||
badgeURL := fmt.Sprintf("%s/actions/workflows/%s/badge.svg?branch=%s", repo.HTMLURL(ctx), util.PathEscapeSegments(entry.Name()), url.QueryEscape(repo.DefaultBranch))
|
badgeURL := fmt.Sprintf("%s/actions/workflows/%s/badge.svg?branch=%s", repo.HTMLURL(ctx), util.PathEscapeSegments(entry.Name()), url.QueryEscape(repo.DefaultBranch))
|
||||||
|
|
||||||
// See https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#get-a-workflow
|
// See https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#get-a-workflow
|
||||||
@ -457,7 +460,7 @@ func ListActionWorkflows(ctx context.Context, gitrepo *git.Repository, repo *rep
|
|||||||
|
|
||||||
workflows := make([]*api.ActionWorkflow, len(entries))
|
workflows := make([]*api.ActionWorkflow, len(entries))
|
||||||
for i, entry := range entries {
|
for i, entry := range entries {
|
||||||
workflows[i] = getActionWorkflowEntry(ctx, repo, defaultBranchCommit, repo.DefaultBranch, folder, entry)
|
workflows[i] = getActionWorkflowEntry(ctx, repo, defaultBranchCommit, git.RefNameFromBranch(repo.DefaultBranch), folder, entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
return workflows, nil
|
return workflows, nil
|
||||||
@ -469,14 +472,35 @@ func GetActionWorkflow(ctx context.Context, gitrepo *git.Repository, repo *repo_
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
folder, entries, err := actions.ListWorkflows(defaultBranchCommit)
|
return getActionWorkflowFromCommit(ctx, repo, defaultBranchCommit, git.RefNameFromBranch(repo.DefaultBranch), workflowID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetActionWorkflowByRef(ctx context.Context, gitrepo *git.Repository, repo *repo_model.Repository, workflowID string, ref git.RefName) (*api.ActionWorkflow, error) {
|
||||||
|
if ref == "" {
|
||||||
|
return nil, util.NewNotExistErrorf("workflow %q not found", workflowID)
|
||||||
|
}
|
||||||
|
|
||||||
|
refCommitID, err := gitrepo.GetRefCommitID(ref.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
refCommit, err := gitrepo.GetCommit(refCommitID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return getActionWorkflowFromCommit(ctx, repo, refCommit, ref, workflowID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActionWorkflowFromCommit(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, refName git.RefName, workflowID string) (*api.ActionWorkflow, error) {
|
||||||
|
folder, entries, err := actions.ListWorkflows(commit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.Name() == workflowID {
|
if entry.Name() == workflowID {
|
||||||
return getActionWorkflowEntry(ctx, repo, defaultBranchCommit, repo.DefaultBranch, folder, entry), nil
|
return getActionWorkflowEntry(ctx, repo, commit, refName, folder, entry), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ package webhook
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
@ -22,6 +23,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/repository"
|
"code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
notify_service "code.gitea.io/gitea/services/notify"
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
@ -1032,7 +1034,10 @@ func (*webhookNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_
|
|||||||
}
|
}
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
convertedWorkflow, err := convert.GetActionWorkflow(ctx, gitRepo, repo, run.WorkflowID)
|
convertedWorkflow, err := convert.GetActionWorkflowByRef(ctx, gitRepo, repo, run.WorkflowID, git.RefName(run.Ref))
|
||||||
|
if err != nil && errors.Is(err, util.ErrNotExist) {
|
||||||
|
convertedWorkflow, err = convert.GetActionWorkflow(ctx, gitRepo, repo, run.WorkflowID)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetActionWorkflow: %v", err)
|
log.Error("GetActionWorkflow: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user