Hold a task's merge while another task in the project is merging
This commit is contained in:
+98
-17
@@ -268,6 +268,9 @@ UPDATE tasks SET port_base = (
|
||||
SELECT 41000 + 10 * (count(*) - 1) FROM tasks AS earlier
|
||||
WHERE earlier.status = 'active' AND (earlier.created_at < tasks.created_at OR (earlier.created_at = tasks.created_at AND earlier.id <= tasks.id))
|
||||
) WHERE status = 'active' AND port_base = 0;
|
||||
`}, {"add-task-merge-queue", `
|
||||
ALTER TABLE tasks ADD COLUMN merge_queued_by TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE tasks ADD COLUMN merge_queued_at TEXT NOT NULL DEFAULT '';
|
||||
`}}
|
||||
|
||||
const (
|
||||
@@ -570,21 +573,7 @@ func (s *Store) CreateTask(ctx context.Context, t api.Task, actor string) error
|
||||
}
|
||||
|
||||
func (s *Store) ListTasks(ctx context.Context, projectID string) ([]api.Task, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT `+taskColumns+` FROM tasks WHERE project_id = ? ORDER BY created_at DESC`, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
tasks := []api.Task{}
|
||||
for rows.Next() {
|
||||
t, err := scanTask(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tasks = append(tasks, t)
|
||||
}
|
||||
return tasks, rows.Err()
|
||||
return s.queryTasks(ctx, `SELECT `+taskColumns+` FROM tasks WHERE project_id = ? ORDER BY created_at DESC`, projectID)
|
||||
}
|
||||
|
||||
func (s *Store) GetTask(ctx context.Context, id string) (api.Task, error) {
|
||||
@@ -633,6 +622,90 @@ func (s *Store) TakeMergingBy(ctx context.Context, id string) (string, error) {
|
||||
return login, err
|
||||
}
|
||||
|
||||
func (s *Store) MergingTasks(ctx context.Context) ([]api.Task, error) {
|
||||
return s.queryTasks(ctx, `SELECT `+taskColumns+` FROM tasks WHERE status = ? AND merging_by != ''`, string(api.TaskStatusActive))
|
||||
}
|
||||
|
||||
func (s *Store) QueuedMerges(ctx context.Context, projectID string) ([]api.Task, error) {
|
||||
return s.queryTasks(ctx, `SELECT `+taskColumns+` FROM tasks WHERE project_id = ? AND status = ? AND merge_queued_by != '' ORDER BY merge_queued_at`,
|
||||
projectID, string(api.TaskStatusActive))
|
||||
}
|
||||
|
||||
func (s *Store) ProjectsWithQueuedMerges(ctx context.Context) ([]string, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT DISTINCT project_id FROM tasks WHERE status = ? AND merge_queued_by != ''`, string(api.TaskStatusActive))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
ids := []string{}
|
||||
for rows.Next() {
|
||||
id := ""
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) queryTasks(ctx context.Context, query string, args ...any) ([]api.Task, error) {
|
||||
rows, err := s.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
tasks := []api.Task{}
|
||||
for rows.Next() {
|
||||
t, err := scanTask(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tasks = append(tasks, t)
|
||||
}
|
||||
return tasks, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) QueueMerge(ctx context.Context, id, login string) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
projectID := ""
|
||||
err := tx.QueryRowContext(ctx, `UPDATE tasks SET merge_queued_by = ?, merge_queued_at = ? WHERE id = ? AND status = ? AND merge_queued_by = '' RETURNING project_id`,
|
||||
login, now(), id, string(api.TaskStatusActive)).Scan(&projectID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(api.Event{Kind: "task.merge_queued", ProjectId: &projectID, TaskId: &id, Actor: login, Payload: map[string]any{"id": id}})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) TakeQueuedMerge(ctx context.Context, id string) (string, error) {
|
||||
login := ""
|
||||
err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
projectID := ""
|
||||
err := tx.QueryRowContext(ctx, `SELECT project_id, merge_queued_by FROM tasks WHERE id = ?`, id).Scan(&projectID, &login)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if login == "" {
|
||||
return nil
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `UPDATE tasks SET merge_queued_by = '', merge_queued_at = '' WHERE id = ?`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(api.Event{Kind: "task.merge_unqueued", ProjectId: &projectID, TaskId: &id, Actor: "byre", Payload: map[string]any{"id": id}})
|
||||
})
|
||||
return login, err
|
||||
}
|
||||
|
||||
func (s *Store) MergeTask(ctx context.Context, id, commit, pushed, actor string) (api.Task, error) {
|
||||
return s.closeTask(ctx, id, api.TaskStatusMerged, commit, actor, "task.merged", map[string]any{"id": id, "commit": commit, "pushed": pushed})
|
||||
}
|
||||
@@ -1007,7 +1080,7 @@ func (s *Store) write(ctx context.Context, fn func(tx *sql.Tx, emit emitFunc) er
|
||||
|
||||
const projectColumns = `id, name, repo_path, default_branch, remote, created_by, created_at`
|
||||
|
||||
const taskColumns = `id, project_id, title, merge_title, branch, worktree_path, base_ref, port_base, status, created_by, created_at, closed_at, merge_commit, todo_item, forked_from, forked_at`
|
||||
const taskColumns = `id, project_id, title, merge_title, branch, worktree_path, base_ref, port_base, status, created_by, created_at, closed_at, merge_commit, todo_item, forked_from, forked_at, merge_queued_at`
|
||||
|
||||
const sessionColumns = `id, coalesce(task_id, ''), owner, vendor, model, vendor_session_id, state, role, created_by, created_at, forked_from, forked_at`
|
||||
|
||||
@@ -1035,7 +1108,8 @@ func scanTask(row scanner) (api.Task, error) {
|
||||
todoItem := ""
|
||||
forkedFrom := ""
|
||||
forkedAt := 0
|
||||
err := row.Scan(&t.Id, &t.ProjectId, &t.Title, &t.MergeTitle, &t.Branch, &t.WorktreePath, &t.BaseRef, &t.PortBase, &status, &t.CreatedBy, &createdAt, &closedAt, &mergeCommit, &todoItem, &forkedFrom, &forkedAt)
|
||||
mergeQueuedAt := ""
|
||||
err := row.Scan(&t.Id, &t.ProjectId, &t.Title, &t.MergeTitle, &t.Branch, &t.WorktreePath, &t.BaseRef, &t.PortBase, &status, &t.CreatedBy, &createdAt, &closedAt, &mergeCommit, &todoItem, &forkedFrom, &forkedAt, &mergeQueuedAt)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
@@ -1064,6 +1138,13 @@ func scanTask(row scanner) (api.Task, error) {
|
||||
t.ForkedFrom = &forkedFrom
|
||||
t.ForkedAt = &forkedAt
|
||||
}
|
||||
if mergeQueuedAt != "" {
|
||||
qt, err := parseTime(mergeQueuedAt)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
t.MergeQueuedAt = &qt
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user