Skip to content

Commit e9bb781

Browse files
committed
Merge remote-tracking branch 'origin/main' into acticons
* origin/main: Allow fast-forward-only merge when signed commits are required (go-gitea#37335) Introduce `ActionRunAttempt` to represent each execution of a run (go-gitea#37119) Move review request functions to a standalone file (go-gitea#37358) Fix repo init README EOL (go-gitea#37388) Fix org team assignee/reviewer lookups for team member permissions (go-gitea#37365) Remove external service dependencies in migration tests (go-gitea#36866) Extend issue context popup beyond markdown content (go-gitea#36908) # Conflicts: # routers/api/v1/repo/action.go # web_src/js/components/RepoActionView.vue
2 parents d6cd40b + 3b2fd97 commit e9bb781

282 files changed

Lines changed: 6975 additions & 1490 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

custom/conf/app.example.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2973,6 +2973,8 @@ LEVEL = Info
29732973
;; Comma-separated list of workflow directories, the first one to exist
29742974
;; in a repo is used to find Actions workflow files
29752975
;WORKFLOW_DIRS = .gitea/workflows,.github/workflows
2976+
;; Maximum number of attempts a single workflow run can have. Default value is 50.
2977+
;MAX_RERUN_ATTEMPTS = 50
29762978

29772979
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
29782980
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

models/actions/artifact.go

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"time"
1313

1414
"code.gitea.io/gitea/models/db"
15+
"code.gitea.io/gitea/modules/optional"
1516
"code.gitea.io/gitea/modules/timeutil"
1617
"code.gitea.io/gitea/modules/util"
1718

@@ -61,7 +62,8 @@ const (
6162
// ActionArtifact is a file that is stored in the artifact storage.
6263
type ActionArtifact struct {
6364
ID int64 `xorm:"pk autoincr"`
64-
RunID int64 `xorm:"index unique(runid_name_path)"` // The run id of the artifact
65+
RunID int64 `xorm:"index unique(runid_attempt_name_path)"` // The run id of the artifact
66+
RunAttemptID int64 `xorm:"index unique(runid_attempt_name_path) NOT NULL DEFAULT 0"`
6567
RunnerID int64
6668
RepoID int64 `xorm:"index"`
6769
OwnerID int64
@@ -80,9 +82,9 @@ type ActionArtifact struct {
8082
// * "application/pdf", "text/html", etc.: real content type of the artifact
8183
ContentEncodingOrType string `xorm:"content_encoding"`
8284

83-
ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it
84-
ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when runner uploads it
85-
Status ArtifactStatus `xorm:"index"` // The status of the artifact, uploading, expired or need-delete
85+
ArtifactPath string `xorm:"index unique(runid_attempt_name_path)"` // The path to the artifact when runner uploads it
86+
ArtifactName string `xorm:"index unique(runid_attempt_name_path)"` // The name of the artifact when runner uploads it
87+
Status ArtifactStatus `xorm:"index"` // The status of the artifact, uploading, expired or need-delete
8688
CreatedUnix timeutil.TimeStamp `xorm:"created"`
8789
UpdatedUnix timeutil.TimeStamp `xorm:"updated index"`
8890
ExpiredUnix timeutil.TimeStamp `xorm:"index"` // The time when the artifact will be expired
@@ -92,12 +94,13 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
9294
if err := t.LoadJob(ctx); err != nil {
9395
return nil, err
9496
}
95-
artifact, err := getArtifactByNameAndPath(ctx, t.Job.RunID, artifactName, artifactPath)
97+
artifact, err := getArtifactByNameAndPath(ctx, t.Job.RunID, t.Job.RunAttemptID, artifactName, artifactPath)
9698
if errors.Is(err, util.ErrNotExist) {
9799
artifact := &ActionArtifact{
98100
ArtifactName: artifactName,
99101
ArtifactPath: artifactPath,
100102
RunID: t.Job.RunID,
103+
RunAttemptID: t.Job.RunAttemptID,
101104
RunnerID: t.RunnerID,
102105
RepoID: t.RepoID,
103106
OwnerID: t.OwnerID,
@@ -122,9 +125,9 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
122125
return artifact, nil
123126
}
124127

125-
func getArtifactByNameAndPath(ctx context.Context, runID int64, name, fpath string) (*ActionArtifact, error) {
128+
func getArtifactByNameAndPath(ctx context.Context, runID, runAttemptID int64, name, fpath string) (*ActionArtifact, error) {
126129
var art ActionArtifact
127-
has, err := db.GetEngine(ctx).Where("run_id = ? AND artifact_name = ? AND artifact_path = ?", runID, name, fpath).Get(&art)
130+
has, err := db.GetEngine(ctx).Where("run_id = ? AND run_attempt_id = ? AND artifact_name = ? AND artifact_path = ?", runID, runAttemptID, name, fpath).Get(&art)
128131
if err != nil {
129132
return nil, err
130133
} else if !has {
@@ -144,6 +147,7 @@ type FindArtifactsOptions struct {
144147
db.ListOptions
145148
RepoID int64
146149
RunID int64
150+
RunAttemptID optional.Option[int64] // use optional to allow filtering by zero (legacy artifacts have run_attempt_id=0)
147151
ArtifactName string
148152
Status int
149153
FinalizedArtifactsV4 bool
@@ -163,6 +167,9 @@ func (opts FindArtifactsOptions) ToConds() builder.Cond {
163167
if opts.RunID > 0 {
164168
cond = cond.And(builder.Eq{"run_id": opts.RunID})
165169
}
170+
if opts.RunAttemptID.Has() {
171+
cond = cond.And(builder.Eq{"run_attempt_id": opts.RunAttemptID.Value()})
172+
}
166173
if opts.ArtifactName != "" {
167174
cond = cond.And(builder.Eq{"artifact_name": opts.ArtifactName})
168175
}
@@ -186,11 +193,12 @@ type ActionArtifactMeta struct {
186193
ExpiredUnix timeutil.TimeStamp
187194
}
188195

189-
// ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run
190-
func ListUploadedArtifactsMeta(ctx context.Context, repoID, runID int64) ([]*ActionArtifactMeta, error) {
196+
// ListUploadedArtifactsMetaByRunAttempt returns uploaded artifacts meta scoped to a specific run and attempt.
197+
// Pass runAttemptID=0 to target legacy artifacts (pre-v331) belonging to the run.
198+
func ListUploadedArtifactsMetaByRunAttempt(ctx context.Context, repoID, runID, runAttemptID int64) ([]*ActionArtifactMeta, error) {
191199
arts := make([]*ActionArtifactMeta, 0, 10)
192200
return arts, db.GetEngine(ctx).Table("action_artifact").
193-
Where("repo_id=? AND run_id=? AND (status=? OR status=?)", repoID, runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired).
201+
Where("repo_id=? AND run_id=? AND run_attempt_id=? AND (status=? OR status=?)", repoID, runID, runAttemptID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired).
194202
GroupBy("artifact_name").
195203
Select("artifact_name, sum(file_size) as file_size, max(status) as status, max(expired_unix) as expired_unix").
196204
Find(&arts)
@@ -217,12 +225,29 @@ func SetArtifactExpired(ctx context.Context, artifactID int64) error {
217225
return err
218226
}
219227

220-
// SetArtifactNeedDelete sets an artifact to need-delete, cron job will delete it
221-
func SetArtifactNeedDelete(ctx context.Context, runID int64, name string) error {
222-
_, err := db.GetEngine(ctx).Where("run_id=? AND artifact_name=? AND status = ?", runID, name, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: ArtifactStatusPendingDeletion})
228+
// SetArtifactNeedDeleteByID sets an artifact to need-delete by ID, cron job will delete it.
229+
func SetArtifactNeedDeleteByID(ctx context.Context, artifactID int64) error {
230+
_, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: ArtifactStatusPendingDeletion})
231+
return err
232+
}
233+
234+
// SetArtifactNeedDeleteByRunAttempt sets an artifact to need-delete in a run attempt, cron job will delete it.
235+
// runAttemptID may be 0 for legacy artifacts created before ActionRunAttempt existed.
236+
func SetArtifactNeedDeleteByRunAttempt(ctx context.Context, runID, runAttemptID int64, name string) error {
237+
_, err := db.GetEngine(ctx).Where("run_id=? AND run_attempt_id=? AND artifact_name=? AND status = ?", runID, runAttemptID, name, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: ArtifactStatusPendingDeletion})
223238
return err
224239
}
225240

241+
// GetArtifactsByRunAttemptAndName returns all artifacts with the given name in the specified run attempt.
242+
// This supports both attempt-scoped data and legacy artifacts with run_attempt_id=0.
243+
func GetArtifactsByRunAttemptAndName(ctx context.Context, runID, runAttemptID int64, artifactName string) ([]*ActionArtifact, error) {
244+
arts := make([]*ActionArtifact, 0)
245+
return arts, db.GetEngine(ctx).
246+
Where("run_id = ? AND run_attempt_id = ? AND artifact_name = ?", runID, runAttemptID, artifactName).
247+
OrderBy("id").
248+
Find(&arts)
249+
}
250+
226251
// SetArtifactDeleted sets an artifact to deleted
227252
func SetArtifactDeleted(ctx context.Context, artifactID int64) error {
228253
_, err := db.GetEngine(ctx).ID(artifactID).Cols("status").Update(&ActionArtifact{Status: ArtifactStatusDeleted})

models/actions/run.go

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
type ActionRun struct {
3131
ID int64
3232
Title string
33-
RepoID int64 `xorm:"unique(repo_index) index(repo_concurrency)"`
33+
RepoID int64 `xorm:"unique(repo_index)"`
3434
Repo *repo_model.Repository `xorm:"-"`
3535
OwnerID int64 `xorm:"index"`
3636
WorkflowID string `xorm:"index"` // the name of workflow file
@@ -50,15 +50,20 @@ type ActionRun struct {
5050
Status Status `xorm:"index"`
5151
Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed
5252
RawConcurrency string // raw concurrency
53-
ConcurrencyGroup string `xorm:"index(repo_concurrency) NOT NULL DEFAULT ''"`
54-
ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"`
55-
// Started and Stopped is used for recording last run time, if rerun happened, they will be reset to 0
53+
54+
// Started and Stopped are identical to the latest attempt after ActionRunAttempt was introduced.
55+
// When a rerun creates a new latest attempt, they are reset until the new attempt starts and stops.
5656
Started timeutil.TimeStamp
5757
Stopped timeutil.TimeStamp
58-
// PreviousDuration is used for recording previous duration
58+
59+
// PreviousDuration is kept only for legacy runs created before ActionRunAttempt existed.
60+
// New runs and reruns no longer update this field and use attempt-scoped durations instead.
5961
PreviousDuration time.Duration
60-
Created timeutil.TimeStamp `xorm:"created"`
61-
Updated timeutil.TimeStamp `xorm:"updated"`
62+
63+
LatestAttemptID int64 `xorm:"index NOT NULL DEFAULT 0"`
64+
65+
Created timeutil.TimeStamp `xorm:"created"`
66+
Updated timeutil.TimeStamp `xorm:"updated"`
6267
}
6368

6469
func init() {
@@ -160,6 +165,31 @@ func (run *ActionRun) Duration() time.Duration {
160165
return d
161166
}
162167

168+
// GetLatestAttempt returns
169+
// - the latest attempt of the run
170+
// - (nil, false, nil) for legacy runs that have no attempt records
171+
func (run *ActionRun) GetLatestAttempt(ctx context.Context) (*ActionRunAttempt, bool, error) {
172+
if run.LatestAttemptID == 0 {
173+
return nil, false, nil
174+
}
175+
attempt, err := GetRunAttemptByRepoAndID(ctx, run.RepoID, run.LatestAttemptID)
176+
if err != nil {
177+
return nil, false, err
178+
}
179+
return attempt, true, nil
180+
}
181+
182+
func (run *ActionRun) GetEffectiveConcurrency(ctx context.Context) (string, bool, error) {
183+
attempt, has, err := run.GetLatestAttempt(ctx)
184+
if err != nil {
185+
return "", false, err
186+
}
187+
if has {
188+
return attempt.ConcurrencyGroup, attempt.ConcurrencyCancel, nil
189+
}
190+
return "", false, nil
191+
}
192+
163193
func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) {
164194
if run.Event == webhook_module.HookEventPush {
165195
var payload api.PushPayload
@@ -406,14 +436,11 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
406436

407437
type ActionRunIndex db.ResourceIndex
408438

409-
func GetConcurrentRunsAndJobs(ctx context.Context, repoID int64, concurrencyGroup string, status []Status) ([]*ActionRun, []*ActionRunJob, error) {
410-
runs, err := db.Find[ActionRun](ctx, &FindRunOptions{
411-
RepoID: repoID,
412-
ConcurrencyGroup: concurrencyGroup,
413-
Status: status,
414-
})
439+
// GetConcurrentRunAttemptsAndJobs returns run attempts and jobs in the same concurrency group by statuses.
440+
func GetConcurrentRunAttemptsAndJobs(ctx context.Context, repoID int64, concurrencyGroup string, status []Status) ([]*ActionRunAttempt, []*ActionRunJob, error) {
441+
attempts, err := FindConcurrentRunAttempts(ctx, repoID, concurrencyGroup, status)
415442
if err != nil {
416-
return nil, nil, fmt.Errorf("find runs: %w", err)
443+
return nil, nil, fmt.Errorf("find run attempts: %w", err)
417444
}
418445

419446
jobs, err := db.Find[ActionRunJob](ctx, &FindRunJobOptions{
@@ -425,36 +452,34 @@ func GetConcurrentRunsAndJobs(ctx context.Context, repoID int64, concurrencyGrou
425452
return nil, nil, fmt.Errorf("find jobs: %w", err)
426453
}
427454

428-
return runs, jobs, nil
455+
return attempts, jobs, nil
429456
}
430457

431-
func CancelPreviousJobsByRunConcurrency(ctx context.Context, actionRun *ActionRun) ([]*ActionRunJob, error) {
432-
if actionRun.ConcurrencyGroup == "" {
458+
func CancelPreviousJobsByRunConcurrency(ctx context.Context, attempt *ActionRunAttempt) ([]*ActionRunJob, error) {
459+
if attempt.ConcurrencyGroup == "" {
433460
return nil, nil
434461
}
435462

436463
var jobsToCancel []*ActionRunJob
437464

438465
statusFindOption := []Status{StatusWaiting, StatusBlocked}
439-
if actionRun.ConcurrencyCancel {
466+
if attempt.ConcurrencyCancel {
440467
statusFindOption = append(statusFindOption, StatusRunning)
441468
}
442-
runs, jobs, err := GetConcurrentRunsAndJobs(ctx, actionRun.RepoID, actionRun.ConcurrencyGroup, statusFindOption)
469+
attempts, jobs, err := GetConcurrentRunAttemptsAndJobs(ctx, attempt.RepoID, attempt.ConcurrencyGroup, statusFindOption)
443470
if err != nil {
444471
return nil, fmt.Errorf("find concurrent runs and jobs: %w", err)
445472
}
446473
jobsToCancel = append(jobsToCancel, jobs...)
447474

448475
// cancel runs in the same concurrency group
449-
for _, run := range runs {
450-
if run.ID == actionRun.ID {
476+
for _, concurrentAttempt := range attempts {
477+
if concurrentAttempt.RunID == attempt.RunID {
451478
continue
452479
}
453-
jobs, err := db.Find[ActionRunJob](ctx, FindRunJobOptions{
454-
RunID: run.ID,
455-
})
480+
jobs, err := GetRunJobsByRunAndAttemptID(ctx, concurrentAttempt.RunID, concurrentAttempt.ID)
456481
if err != nil {
457-
return nil, fmt.Errorf("find run %d jobs: %w", run.ID, err)
482+
return nil, fmt.Errorf("find run %d attempt %d jobs: %w", concurrentAttempt.RunID, concurrentAttempt.ID, err)
458483
}
459484
jobsToCancel = append(jobsToCancel, jobs...)
460485
}

0 commit comments

Comments
 (0)