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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user