1
1
mirror of https://github.com/go-gitea/gitea synced 2025-12-07 13:28:25 +00:00

Merge remote-tracking branch 'origin/main' into api-repo-actions

This commit is contained in:
chesterip
2023-08-30 11:31:23 -04:00
317 changed files with 3111 additions and 2686 deletions
+104 -4
View File
@@ -4,6 +4,7 @@
package actions
import (
"bytes"
"context"
"fmt"
"strings"
@@ -24,6 +25,7 @@ import (
"code.gitea.io/gitea/services/convert"
"github.com/nektos/act/pkg/jobparser"
"github.com/nektos/act/pkg/model"
)
var methodCtxKey struct{}
@@ -143,15 +145,15 @@ func notify(ctx context.Context, input *notifyInput) error {
}
var detectedWorkflows []*actions_module.DetectedWorkflow
workflows, err := actions_module.DetectWorkflows(gitRepo, commit, input.Event, input.Payload)
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit, input.Event, input.Payload)
if err != nil {
return fmt.Errorf("DetectWorkflows: %w", err)
}
if len(workflows) == 0 {
log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID)
} else {
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
for _, wf := range workflows {
if actionsConfig.IsWorkflowDisabled(wf.EntryName) {
log.Trace("repo %s has disable workflows %s", input.Repo.RepoPath(), wf.EntryName)
@@ -171,7 +173,7 @@ func notify(ctx context.Context, input *notifyInput) error {
if err != nil {
return fmt.Errorf("gitRepo.GetCommit: %w", err)
}
baseWorkflows, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload)
baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload)
if err != nil {
return fmt.Errorf("DetectWorkflows: %w", err)
}
@@ -186,7 +188,22 @@ func notify(ctx context.Context, input *notifyInput) error {
}
}
if err := handleSchedules(ctx, schedules, commit, input); err != nil {
return err
}
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref)
}
func handleWorkflows(
ctx context.Context,
detectedWorkflows []*actions_module.DetectedWorkflow,
commit *git.Commit,
input *notifyInput,
ref string,
) error {
if len(detectedWorkflows) == 0 {
log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID)
return nil
}
@@ -350,3 +367,86 @@ func ifNeedApproval(ctx context.Context, run *actions_model.ActionRun, repo *rep
log.Trace("need approval because it's the first time user %d triggered actions", user.ID)
return true, nil
}
func handleSchedules(
ctx context.Context,
detectedWorkflows []*actions_module.DetectedWorkflow,
commit *git.Commit,
input *notifyInput,
) error {
if len(detectedWorkflows) == 0 {
log.Trace("repo %s with commit %s couldn't find schedules", input.Repo.RepoPath(), commit.ID)
return nil
}
branch, err := commit.GetBranchName()
if err != nil {
return err
}
if branch != input.Repo.DefaultBranch {
log.Trace("commit branch is not default branch in repo")
return nil
}
rows, _, err := actions_model.FindSchedules(ctx, actions_model.FindScheduleOptions{RepoID: input.Repo.ID})
if err != nil {
log.Error("FindCrons: %v", err)
return err
}
if len(rows) > 0 {
if err := actions_model.DeleteScheduleTaskByRepo(ctx, input.Repo.ID); err != nil {
log.Error("DeleteCronTaskByRepo: %v", err)
}
}
p, err := json.Marshal(input.Payload)
if err != nil {
return fmt.Errorf("json.Marshal: %w", err)
}
crons := make([]*actions_model.ActionSchedule, 0, len(detectedWorkflows))
for _, dwf := range detectedWorkflows {
// Check cron job condition. Only working in default branch
workflow, err := model.ReadWorkflow(bytes.NewReader(dwf.Content))
if err != nil {
log.Error("ReadWorkflow: %v", err)
continue
}
schedules := workflow.OnSchedule()
if len(schedules) == 0 {
log.Warn("no schedule event")
continue
}
run := &actions_model.ActionSchedule{
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
RepoID: input.Repo.ID,
OwnerID: input.Repo.OwnerID,
WorkflowID: dwf.EntryName,
TriggerUserID: input.Doer.ID,
Ref: input.Ref,
CommitSHA: commit.ID.String(),
Event: input.Event,
EventPayload: string(p),
Specs: schedules,
Content: dwf.Content,
}
// cancel running jobs if the event is push
if run.Event == webhook_module.HookEventPush {
// cancel running jobs of the same workflow
if err := actions_model.CancelRunningJobs(
ctx,
run.RepoID,
run.Ref,
run.WorkflowID,
); err != nil {
log.Error("CancelRunningJobs: %v", err)
}
}
crons = append(crons, run)
}
return actions_model.CreateScheduleTask(ctx, crons)
}
+135
View File
@@ -0,0 +1,135 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"context"
"fmt"
"time"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/nektos/act/pkg/jobparser"
)
// StartScheduleTasks start the task
func StartScheduleTasks(ctx context.Context) error {
return startTasks(ctx)
}
// startTasks retrieves specifications in pages, creates a schedule task for each specification,
// and updates the specification's next run time and previous run time.
// The function returns an error if there's an issue with finding or updating the specifications.
func startTasks(ctx context.Context) error {
// Set the page size
pageSize := 50
// Retrieve specs in pages until all specs have been retrieved
now := time.Now()
for page := 1; ; page++ {
// Retrieve the specs for the current page
specs, _, err := actions_model.FindSpecs(ctx, actions_model.FindSpecOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: pageSize,
},
Next: now.Unix(),
})
if err != nil {
return fmt.Errorf("find specs: %w", err)
}
// Loop through each spec and create a schedule task for it
for _, row := range specs {
// cancel running jobs if the event is push
if row.Schedule.Event == webhook_module.HookEventPush {
// cancel running jobs of the same workflow
if err := actions_model.CancelRunningJobs(
ctx,
row.RepoID,
row.Schedule.Ref,
row.Schedule.WorkflowID,
); err != nil {
log.Error("CancelRunningJobs: %v", err)
}
}
if err := CreateScheduleTask(ctx, row.Schedule); err != nil {
log.Error("CreateScheduleTask: %v", err)
return err
}
// Parse the spec
schedule, err := row.Parse()
if err != nil {
log.Error("Parse: %v", err)
return err
}
// Update the spec's next run time and previous run time
row.Prev = row.Next
row.Next = timeutil.TimeStamp(schedule.Next(now.Add(1 * time.Minute)).Unix())
if err := actions_model.UpdateScheduleSpec(ctx, row, "prev", "next"); err != nil {
log.Error("UpdateScheduleSpec: %v", err)
return err
}
}
// Stop if all specs have been retrieved
if len(specs) < pageSize {
break
}
}
return nil
}
// CreateScheduleTask creates a scheduled task from a cron action schedule.
// It creates an action run based on the schedule, inserts it into the database, and creates commit statuses for each job.
func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule) error {
// Create a new action run based on the schedule
run := &actions_model.ActionRun{
Title: cron.Title,
RepoID: cron.RepoID,
OwnerID: cron.OwnerID,
WorkflowID: cron.WorkflowID,
TriggerUserID: cron.TriggerUserID,
Ref: cron.Ref,
CommitSHA: cron.CommitSHA,
Event: cron.Event,
EventPayload: cron.EventPayload,
Status: actions_model.StatusWaiting,
}
// Parse the workflow specification from the cron schedule
workflows, err := jobparser.Parse(cron.Content)
if err != nil {
return err
}
// Insert the action run and its associated jobs into the database
if err := actions_model.InsertRun(ctx, run, workflows); err != nil {
return err
}
// Retrieve the jobs for the newly created action run
jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: run.ID})
if err != nil {
return err
}
// Create commit statuses for each job
for _, job := range jobs {
if err := createCommitStatus(ctx, job); err != nil {
return err
}
}
// Return nil if no errors occurred
return nil
}
+4 -3
View File
@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
auth_module "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
source_service "code.gitea.io/gitea/services/auth/source"
@@ -41,7 +42,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
usernameUsers := make(map[string]*user_model.User, len(users))
mailUsers := make(map[string]*user_model.User, len(users))
keepActiveUsers := make(map[int64]struct{})
keepActiveUsers := make(container.Set[int64])
for _, u := range users {
usernameUsers[u.LowerName] = u
@@ -97,7 +98,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
}
if usr != nil {
keepActiveUsers[usr.ID] = struct{}{}
keepActiveUsers.Add(usr.ID)
} else if len(su.Username) == 0 {
// we cannot create the user if su.Username is empty
continue
@@ -208,7 +209,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
// Deactivate users not present in LDAP
if updateExisting {
for _, usr := range users {
if _, ok := keepActiveUsers[usr.ID]; ok {
if keepActiveUsers.Contains(usr.ID) {
continue
}
+1
View File
@@ -22,6 +22,7 @@ func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_mode
HTMLURL: r.HTMLURL(),
TarURL: r.TarURL(),
ZipURL: r.ZipURL(),
UploadURL: r.APIUploadURL(),
IsDraft: r.IsDraft,
IsPrerelease: r.IsPrerelease,
CreatedAt: r.CreatedUnix.AsTime(),
+28
View File
@@ -0,0 +1,28 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package convert
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 TestRelease_ToRelease(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
release1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: 1})
release1.LoadAttributes(db.DefaultContext)
apiRelease := ToAPIRelease(db.DefaultContext, repo1, release1)
assert.NotNil(t, apiRelease)
assert.EqualValues(t, 1, apiRelease.ID)
assert.EqualValues(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1", apiRelease.URL)
assert.EqualValues(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1/assets", apiRelease.UploadURL)
}
+14
View File
@@ -18,6 +18,7 @@ func initActionsTasks() {
registerStopZombieTasks()
registerStopEndlessTasks()
registerCancelAbandonedJobs()
registerScheduleTasks()
}
func registerStopZombieTasks() {
@@ -49,3 +50,16 @@ func registerCancelAbandonedJobs() {
return actions_service.CancelAbandonedJobs(ctx)
})
}
// registerScheduleTasks registers a scheduled task that runs every minute to start any due schedule tasks.
func registerScheduleTasks() {
// Register the task with a unique name, enabled status, and schedule for every minute.
RegisterTaskFatal("start_schedule_tasks", &BaseConfig{
Enabled: true,
RunAtStart: false,
Schedule: "@every 1m",
}, func(ctx context.Context, _ *user_model.User, cfg Config) error {
// Call the function to start schedule tasks and pass the context.
return actions_service.StartScheduleTasks(ctx)
})
}
+2 -1
View File
@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/models/user"
gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
@@ -41,7 +42,7 @@ func TestProcessorHelper(t *testing.T) {
assert.NoError(t, err)
base, baseCleanUp := gitea_context.NewBaseContext(httptest.NewRecorder(), req)
defer baseCleanUp()
giteaCtx := &gitea_context.Context{Base: base}
giteaCtx := gitea_context.NewWebContext(base, &test.MockRender{}, nil)
assert.True(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPublic))
assert.False(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPrivate))
+3 -3
View File
@@ -14,6 +14,7 @@ import (
"strings"
"time"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/structs"
@@ -673,16 +674,15 @@ func (g *GitlabDownloader) GetReviews(reviewable base.Reviewable) ([]*base.Revie
func (g *GitlabDownloader) awardsToReactions(awards []*gitlab.AwardEmoji) []*base.Reaction {
result := make([]*base.Reaction, 0, len(awards))
uniqCheck := make(map[string]struct{})
uniqCheck := make(container.Set[string])
for _, award := range awards {
uid := fmt.Sprintf("%s%d", award.Name, award.User.ID)
if _, ok := uniqCheck[uid]; !ok {
if uniqCheck.Add(uid) {
result = append(result, &base.Reaction{
UserID: int64(award.User.ID),
UserName: award.User.Username,
Content: award.Name,
})
uniqCheck[uid] = struct{}{}
}
}
return result
+10 -11
View File
@@ -10,21 +10,20 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
mirror_module "code.gitea.io/gitea/modules/mirror"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
)
// doMirrorSync causes this request to mirror itself
func doMirrorSync(ctx context.Context, req *mirror_module.SyncRequest) {
func doMirrorSync(ctx context.Context, req *SyncRequest) {
if req.ReferenceID == 0 {
log.Warn("Skipping mirror sync request, no mirror ID was specified")
return
}
switch req.Type {
case mirror_module.PushMirrorType:
case PushMirrorType:
_ = SyncPushMirror(ctx, req.ReferenceID)
case mirror_module.PullMirrorType:
case PullMirrorType:
_ = SyncPullMirror(ctx, req.ReferenceID)
default:
log.Error("Unknown Request type in queue: %v for MirrorID[%d]", req.Type, req.ReferenceID)
@@ -43,7 +42,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
handler := func(idx int, bean any) error {
var repo *repo_model.Repository
var mirrorType mirror_module.SyncType
var mirrorType SyncType
var referenceID int64
if m, ok := bean.(*repo_model.Mirror); ok {
@@ -52,7 +51,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
return nil
}
repo = m.Repo
mirrorType = mirror_module.PullMirrorType
mirrorType = PullMirrorType
referenceID = m.RepoID
} else if m, ok := bean.(*repo_model.PushMirror); ok {
if m.GetRepository() == nil {
@@ -60,7 +59,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
return nil
}
repo = m.Repo
mirrorType = mirror_module.PushMirrorType
mirrorType = PushMirrorType
referenceID = m.ID
} else {
log.Error("Unknown bean: %v", bean)
@@ -75,9 +74,9 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
}
// Push to the Queue
if err := mirror_module.PushToQueue(mirrorType, referenceID); err != nil {
if err := PushToQueue(mirrorType, referenceID); err != nil {
if err == queue.ErrAlreadyInQueue {
if mirrorType == mirror_module.PushMirrorType {
if mirrorType == PushMirrorType {
log.Trace("PushMirrors for %-v already queued for sync", repo)
} else {
log.Trace("PullMirrors for %-v already queued for sync", repo)
@@ -120,7 +119,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
return nil
}
func queueHandler(items ...*mirror_module.SyncRequest) []*mirror_module.SyncRequest {
func queueHandler(items ...*SyncRequest) []*SyncRequest {
for _, req := range items {
doMirrorSync(graceful.GetManager().ShutdownContext(), req)
}
@@ -129,5 +128,5 @@ func queueHandler(items ...*mirror_module.SyncRequest) []*mirror_module.SyncRequ
// InitSyncMirrors initializes a go routine to sync the mirrors
func InitSyncMirrors() {
mirror_module.StartSyncMirrors(queueHandler)
StartSyncMirrors(queueHandler)
}
+12
View File
@@ -253,3 +253,15 @@ func pushAllLFSObjects(ctx context.Context, gitRepo *git.Repository, lfsClient l
return nil
}
func syncPushMirrorWithSyncOnCommit(ctx context.Context, repoID int64) {
pushMirrors, err := repo_model.GetPushMirrorsSyncedOnCommit(ctx, repoID)
if err != nil {
log.Error("repo_model.GetPushMirrorsSyncedOnCommit failed: %v", err)
return
}
for _, mirror := range pushMirrors {
AddPushMirrorToQueue(mirror.ID)
}
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package mirror
import (
"context"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/notification/base"
"code.gitea.io/gitea/modules/repository"
)
func init() {
notification.RegisterNotifier(&mirrorNotifier{})
}
type mirrorNotifier struct {
base.NullNotifier
}
var _ base.Notifier = &mirrorNotifier{}
func (m *mirrorNotifier) NotifyPushCommits(ctx context.Context, _ *user_model.User, repo *repo_model.Repository, _ *repository.PushUpdateOptions, _ *repository.PushCommits) {
syncPushMirrorWithSyncOnCommit(ctx, repo.ID)
}
func (m *mirrorNotifier) NotifySyncPushCommits(ctx context.Context, _ *user_model.User, repo *repo_model.Repository, _ *repository.PushUpdateOptions, _ *repository.PushCommits) {
syncPushMirrorWithSyncOnCommit(ctx, repo.ID)
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package mirror
import (
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
)
var mirrorQueue *queue.WorkerPoolQueue[*SyncRequest]
// SyncType type of sync request
type SyncType int
const (
// PullMirrorType for pull mirrors
PullMirrorType SyncType = iota
// PushMirrorType for push mirrors
PushMirrorType
)
// SyncRequest for the mirror queue
type SyncRequest struct {
Type SyncType
ReferenceID int64 // RepoID for pull mirror, MirrorID for push mirror
}
// StartSyncMirrors starts a go routine to sync the mirrors
func StartSyncMirrors(queueHandle func(data ...*SyncRequest) []*SyncRequest) {
if !setting.Mirror.Enabled {
return
}
mirrorQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "mirror", queueHandle)
if mirrorQueue == nil {
log.Fatal("Unable to create mirror queue")
}
go graceful.GetManager().RunWithCancel(mirrorQueue)
}
// AddPullMirrorToQueue adds repoID to mirror queue
func AddPullMirrorToQueue(repoID int64) {
addMirrorToQueue(PullMirrorType, repoID)
}
// AddPushMirrorToQueue adds the push mirror to the queue
func AddPushMirrorToQueue(mirrorID int64) {
addMirrorToQueue(PushMirrorType, mirrorID)
}
func addMirrorToQueue(syncType SyncType, referenceID int64) {
if !setting.Mirror.Enabled {
return
}
go func() {
if err := PushToQueue(syncType, referenceID); err != nil {
log.Error("Unable to push sync request for to the queue for pull mirror repo[%d]. Error: %v", referenceID, err)
}
}()
}
// PushToQueue adds the sync request to the queue
func PushToQueue(mirrorType SyncType, referenceID int64) error {
return mirrorQueue.Push(&SyncRequest{
Type: mirrorType,
ReferenceID: referenceID,
})
}
+9 -6
View File
@@ -21,6 +21,7 @@ import (
cargo_module "code.gitea.io/gitea/modules/packages/cargo"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
files_service "code.gitea.io/gitea/services/repository/files"
)
@@ -220,14 +221,16 @@ func getOrCreateIndexRepository(ctx context.Context, doer, owner *user_model.Use
}
type Config struct {
DownloadURL string `json:"dl"`
APIURL string `json:"api"`
DownloadURL string `json:"dl"`
APIURL string `json:"api"`
AuthRequired bool `json:"auth-required"`
}
func BuildConfig(owner *user_model.User) *Config {
func BuildConfig(owner *user_model.User, isPrivate bool) *Config {
return &Config{
DownloadURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo/api/v1/crates",
APIURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo",
DownloadURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo/api/v1/crates",
APIURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo",
AuthRequired: isPrivate,
}
}
@@ -239,7 +242,7 @@ func createOrUpdateConfigFile(ctx context.Context, repo *repo_model.Repository,
"Initialize Cargo Config",
func(t *files_service.TemporaryUploadRepository) error {
var b bytes.Buffer
err := json.NewEncoder(&b).Encode(BuildConfig(owner))
err := json.NewEncoder(&b).Encode(BuildConfig(owner, setting.Service.RequireSignInView || owner.Visibility != structs.VisibleTypePublic || repo.IsPrivate))
if err != nil {
return err
}
+1 -1
View File
@@ -232,7 +232,7 @@ func buildRepomd(pv *packages_model.PackageVersion, ownerID int64, data []*repoD
}
var buf bytes.Buffer
buf.Write([]byte(xml.Header))
buf.WriteString(xml.Header)
if err := xml.NewEncoder(&buf).Encode(&Repomd{
Xmlns: "http://linux.duke.edu/metadata/repo",
XmlnsRpm: "http://linux.duke.edu/metadata/rpm",
+2 -2
View File
@@ -755,11 +755,11 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
}
for _, author := range authors {
if _, err := stringBuilder.Write([]byte("Co-authored-by: ")); err != nil {
if _, err := stringBuilder.WriteString("Co-authored-by: "); err != nil {
log.Error("Unable to write to string builder Error: %v", err)
return ""
}
if _, err := stringBuilder.Write([]byte(author)); err != nil {
if _, err := stringBuilder.WriteString(author); err != nil {
log.Error("Unable to write to string builder Error: %v", err)
return ""
}
+2 -2
View File
@@ -333,9 +333,9 @@ func DismissApprovalReviews(ctx context.Context, doer *user_model.User, pull *is
return err
}
return db.WithTx(ctx, func(subCtx context.Context) error {
return db.WithTx(ctx, func(ctx context.Context) error {
for _, review := range reviews {
if err := issues_model.DismissReview(subCtx, review, true); err != nil {
if err := issues_model.DismissReview(ctx, review, true); err != nil {
return err
}
+3 -3
View File
@@ -114,12 +114,12 @@ func (t *TemporaryUploadRepository) LsFiles(filenames ...string) ([]string, erro
return nil, err
}
filelist := make([]string, len(filenames))
fileList := make([]string, 0, len(filenames))
for _, line := range bytes.Split(stdOut.Bytes(), []byte{'\000'}) {
filelist = append(filelist, string(line))
fileList = append(fileList, string(line))
}
return filelist, nil
return fileList, nil
}
// RemoveFilesFromIndex removes the given files from the index
+26 -9
View File
@@ -97,23 +97,40 @@ func (f *FeishuPayload) Push(p *api.PushPayload) (api.Payloader, error) {
// Issue implements PayloadConvertor Issue method
func (f *FeishuPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true)
return newFeishuTextPayload(issueTitle + "\r\n" + text + "\r\n\r\n" + attachmentText), nil
title, link, by, operator, result, assignees := getIssuesInfo(p)
var res api.Payloader
if assignees != "" {
if p.Action == api.HookIssueAssigned || p.Action == api.HookIssueUnassigned || p.Action == api.HookIssueMilestoned {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.Issue.Body))
} else {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.Issue.Body))
}
} else {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.Issue.Body))
}
return res, nil
}
// IssueComment implements PayloadConvertor IssueComment method
func (f *FeishuPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true)
return newFeishuTextPayload(issueTitle + "\r\n" + text + "\r\n\r\n" + p.Comment.Body), nil
title, link, by, operator := getIssuesCommentInfo(p)
return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.Comment.Body)), nil
}
// PullRequest implements PayloadConvertor PullRequest method
func (f *FeishuPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true)
return newFeishuTextPayload(issueTitle + "\r\n" + text + "\r\n\r\n" + attachmentText), nil
title, link, by, operator, result, assignees := getPullRequestInfo(p)
var res api.Payloader
if assignees != "" {
if p.Action == api.HookIssueAssigned || p.Action == api.HookIssueUnassigned || p.Action == api.HookIssueMilestoned {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.PullRequest.Body))
} else {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.PullRequest.Body))
}
} else {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.PullRequest.Body))
}
return res, nil
}
// Review implements PayloadConvertor Review method
+5 -5
View File
@@ -72,7 +72,7 @@ func TestFeishuPayload(t *testing.T) {
require.NotNil(t, pl)
require.IsType(t, &FeishuPayload{}, pl)
assert.Equal(t, "#2 crash\r\n[test/repo] Issue opened: #2 crash by user1\r\n\r\nissue body", pl.(*FeishuPayload).Content.Text)
assert.Equal(t, "[Issue-test/repo #2]: opened\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.(*FeishuPayload).Content.Text)
p.Action = api.HookIssueClosed
pl, err = d.Issue(p)
@@ -80,7 +80,7 @@ func TestFeishuPayload(t *testing.T) {
require.NotNil(t, pl)
require.IsType(t, &FeishuPayload{}, pl)
assert.Equal(t, "#2 crash\r\n[test/repo] Issue closed: #2 crash by user1", pl.(*FeishuPayload).Content.Text)
assert.Equal(t, "[Issue-test/repo #2]: closed\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.(*FeishuPayload).Content.Text)
})
t.Run("IssueComment", func(t *testing.T) {
@@ -92,7 +92,7 @@ func TestFeishuPayload(t *testing.T) {
require.NotNil(t, pl)
require.IsType(t, &FeishuPayload{}, pl)
assert.Equal(t, "#2 crash\r\n[test/repo] New comment on issue #2 crash by user1\r\n\r\nmore info needed", pl.(*FeishuPayload).Content.Text)
assert.Equal(t, "[Comment-test/repo #2]: created\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\n\nmore info needed", pl.(*FeishuPayload).Content.Text)
})
t.Run("PullRequest", func(t *testing.T) {
@@ -104,7 +104,7 @@ func TestFeishuPayload(t *testing.T) {
require.NotNil(t, pl)
require.IsType(t, &FeishuPayload{}, pl)
assert.Equal(t, "#12 Fix bug\r\n[test/repo] Pull request opened: #12 Fix bug by user1\r\n\r\nfixes bug #2", pl.(*FeishuPayload).Content.Text)
assert.Equal(t, "[PullRequest-test/repo #12]: opened\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\nAssignees: user1\n\nfixes bug #2", pl.(*FeishuPayload).Content.Text)
})
t.Run("PullRequestComment", func(t *testing.T) {
@@ -116,7 +116,7 @@ func TestFeishuPayload(t *testing.T) {
require.NotNil(t, pl)
require.IsType(t, &FeishuPayload{}, pl)
assert.Equal(t, "#12 Fix bug\r\n[test/repo] New comment on pull request #12 Fix bug by user1\r\n\r\nchanges requested", pl.(*FeishuPayload).Content.Text)
assert.Equal(t, "[Comment-test/repo #12]: created\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\n\nchanges requested", pl.(*FeishuPayload).Content.Text)
})
t.Run("Review", func(t *testing.T) {
+63
View File
@@ -28,6 +28,69 @@ func htmlLinkFormatter(url, text string) string {
return fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(url), html.EscapeString(text))
}
// getPullRequestInfo gets the information for a pull request
func getPullRequestInfo(p *api.PullRequestPayload) (title, link, by, operator, operateResult, assignees string) {
title = fmt.Sprintf("[PullRequest-%s #%d]: %s\n%s", p.Repository.FullName, p.PullRequest.Index, p.Action, p.PullRequest.Title)
assignList := p.PullRequest.Assignees
assignStringList := make([]string, len(assignList))
for i, user := range assignList {
assignStringList[i] = user.UserName
}
if p.Action == api.HookIssueAssigned {
operateResult = fmt.Sprintf("%s assign this to %s", p.Sender.UserName, assignList[len(assignList)-1].UserName)
} else if p.Action == api.HookIssueUnassigned {
operateResult = fmt.Sprintf("%s unassigned this for someone", p.Sender.UserName)
} else if p.Action == api.HookIssueMilestoned {
operateResult = fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID)
}
link = p.PullRequest.HTMLURL
by = fmt.Sprintf("PullRequest by %s", p.PullRequest.Poster.UserName)
if len(assignStringList) > 0 {
assignees = fmt.Sprintf("Assignees: %s", strings.Join(assignStringList, ", "))
}
operator = fmt.Sprintf("Operator: %s", p.Sender.UserName)
return title, link, by, operator, operateResult, assignees
}
// getIssuesInfo gets the information for an issue
func getIssuesInfo(p *api.IssuePayload) (issueTitle, link, by, operator, operateResult, assignees string) {
issueTitle = fmt.Sprintf("[Issue-%s #%d]: %s\n%s", p.Repository.FullName, p.Issue.Index, p.Action, p.Issue.Title)
assignList := p.Issue.Assignees
assignStringList := make([]string, len(assignList))
for i, user := range assignList {
assignStringList[i] = user.UserName
}
if p.Action == api.HookIssueAssigned {
operateResult = fmt.Sprintf("%s assign this to %s", p.Sender.UserName, assignList[len(assignList)-1].UserName)
} else if p.Action == api.HookIssueUnassigned {
operateResult = fmt.Sprintf("%s unassigned this for someone", p.Sender.UserName)
} else if p.Action == api.HookIssueMilestoned {
operateResult = fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID)
}
link = p.Issue.HTMLURL
by = fmt.Sprintf("Issue by %s", p.Issue.Poster.UserName)
if len(assignStringList) > 0 {
assignees = fmt.Sprintf("Assignees: %s", strings.Join(assignStringList, ", "))
}
operator = fmt.Sprintf("Operator: %s", p.Sender.UserName)
return issueTitle, link, by, operator, operateResult, assignees
}
// getIssuesCommentInfo gets the information for a comment
func getIssuesCommentInfo(p *api.IssueCommentPayload) (title, link, by, operator string) {
title = fmt.Sprintf("[Comment-%s #%d]: %s\n%s", p.Repository.FullName, p.Issue.Index, p.Action, p.Issue.Title)
link = p.Issue.HTMLURL
if p.IsPull {
by = fmt.Sprintf("PullRequest by %s", p.Issue.Poster.UserName)
} else {
by = fmt.Sprintf("Issue by %s", p.Issue.Poster.UserName)
}
operator = fmt.Sprintf("Operator: %s", p.Sender.UserName)
return title, link, by, operator
}
func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) {
repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
issueTitle := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title)
+17 -1
View File
@@ -123,6 +123,10 @@ func issueTestPayload() *api.IssuePayload {
HTMLURL: "http://localhost:3000/test/repo/issues/2",
Title: "crash",
Body: "issue body",
Poster: &api.User{
UserName: "user1",
AvatarURL: "http://localhost:3000/user1/avatar",
},
Assignees: []*api.User{
{
UserName: "user1",
@@ -161,7 +165,11 @@ func issueCommentTestPayload() *api.IssueCommentPayload {
URL: "http://localhost:3000/api/v1/repos/test/repo/issues/2",
HTMLURL: "http://localhost:3000/test/repo/issues/2",
Title: "crash",
Body: "this happened",
Poster: &api.User{
UserName: "user1",
AvatarURL: "http://localhost:3000/user1/avatar",
},
Body: "this happened",
},
}
}
@@ -190,6 +198,10 @@ func pullRequestCommentTestPayload() *api.IssueCommentPayload {
HTMLURL: "http://localhost:3000/test/repo/pulls/12",
Title: "Fix bug",
Body: "fixes bug #2",
Poster: &api.User{
UserName: "user1",
AvatarURL: "http://localhost:3000/user1/avatar",
},
},
IsPull: true,
}
@@ -254,6 +266,10 @@ func pullRequestTestPayload() *api.PullRequestPayload {
Title: "Fix bug",
Body: "fixes bug #2",
Mergeable: true,
Poster: &api.User{
UserName: "user1",
AvatarURL: "http://localhost:3000/user1/avatar",
},
Assignees: []*api.User{
{
UserName: "user1",