mergequeue

package
v0.9.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2025 License: AGPL-3.0 Imports: 33 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrAlreadyExists       = errors.New("already exist")
	ErrNotFound            = errors.New("not found")
	ErrPullRequestIsClosed = errors.New("pull request is closed")
)

Functions

This section is empty.

Types

type Action

type Action int
const (
	ActionUndefined Action = iota
	ActionNone
	ActionSuspend
	ActionCreatePullRequestComment
	ActionUpdateStateUnchanged
	ActionCreateSuccessfulGithubStatus
	ActionWaitForMerge
	ActionAddFirstInQueueGithubLabel
	ActionTriggerCIJobs
)

func (Action) String

func (a Action) String() string

type BaseBranch

type BaseBranch struct {
	BranchID
	Logfields []zap.Field
}

BaseBranch represents a base branch of a pull request

func NewBaseBranch

func NewBaseBranch(owner, repo, branch string) (*BaseBranch, error)

func (*BaseBranch) String

func (b *BaseBranch) String() string

type BranchID

type BranchID struct {
	RepositoryOwner string
	Repository      string
	Branch          string
}

BranchID identifies a github branch uniquely

func (*BranchID) String

func (b *BranchID) String() string

type CI

type CI struct {
	Client JenkinsClient
	// Jobs is a map of github-status-context -> JobTemplate.
	Jobs map[string]*jenkins.JobTemplate
	// contains filtered or unexported fields
}

func (*CI) Run

func (c *CI) Run(ctx context.Context, pr *PullRequest, ciContext ...string) error

Run starts a build for CI jobs that have as context ciContext. [pr.LastStartedCIBuilds] is overwritten with the URLs of the started builds.

type Config

type Config struct {
	Logger *zap.Logger

	GitHubClient GithubClient
	// CI configures Jenkins Job for that builds are scheduled.
	CI *CI
	// EventChan is a channel from that Github webhook events are received
	EventChan <-chan *github_prov.Event
	Retryer   *retry.Retryer
	// MonitoredRepositories defines the GitHub repositories for that merge
	// queues are created.
	MonitoredRepositories map[Repository]struct{}
	// TriggerOnAutomerge enables adding a pull request to the merge queue
	// when automerge in GitHub is enabled
	TriggerOnAutomerge bool
	// TriggerLabels is a set of GitHub labels that when addded to a PR,
	// cause that the PR is enqueued
	TriggerLabels set.Set[string]
	// HeadLabel is the name of a GitHub label that is added/remove to the
	// first pull request in the mergequeue.
	HeadLabel string
	// DryRun can be enabled to simulate GitHub API and Jenkins Client
	// operations that would result in a change.
	DryRun bool
}

type Coordinator

type Coordinator struct {
	*Config
	// contains filtered or unexported fields
}

Coordinator parses GitHub webhook events for Pull Requests and manages the merge-queues. It adds and removes PRs to merge queues when the [Config.TriggerOnAutomerge] or [Config.TriggerLabels] condition is met. For each git branch, that is a base branch for a PR in the merge-queue, a separate [queue] is created. Webhook events like the submission of a new Check status result in operations on the queue.

func NewCoordinator

func NewCoordinator(cfg Config) *Coordinator

func (*Coordinator) ChangeBaseBranch

func (a *Coordinator) ChangeBaseBranch(
	ctx context.Context,
	oldBaseBranch, newBaseBranch *BaseBranch,
	prNumber int,
) error

ChangeBaseBranch moves a pull request from the oldBaseBranch queue to the one for newBaseBranch.

func (*Coordinator) Dequeue

func (a *Coordinator) Dequeue(_ context.Context, baseBranch *BaseBranch, prNumber int, setPendingStatusState bool) (*PullRequest, error)

Dequeue removes the pull request with number prNumber from the queue of baseBranch. If no pull request is queued with prNumber an ErrNotFound error is returned. If the pull request is the only element in the baseBranch queue, the queue is removed.

func (*Coordinator) Enqueue

func (a *Coordinator) Enqueue(_ context.Context, baseBranch *BaseBranch, pr *PullRequest) error

Enqueue adds the pull request to the mergequeue of baseBranch. When it becomes the first element in the queue and it has no negative merge-requirements, it will be processed. This means CI jobs will be triggered, it is kept up to date with it's base branch, it is labeled and a commit status is submitted. If the pr is already enqueued an ErrAlreadyExists error is returned. If no queue for the baseBranch exist, it will be created.

func (*Coordinator) InitSync

func (a *Coordinator) InitSync(ctx context.Context) error

InitSync does an initial synchronization of the autoupdater queues with the pull request state at GitHub. It must be run **before** Autoupdater is started. Pull request information is queried from github. If a PR meets a condition to be enqueued for auto-updates it is enqueued. If it meets a condition for not being automatically updated, it is dequeued. If a PR has the [a.HeadLabel] it is removed, if it has a non-pending github status from directorius it is set to pending.

func (*Coordinator) PauseQueue

func (a *Coordinator) PauseQueue(baseBranch *BranchID)

PauseQueue pauses the merge queue. Pull request can still be added and removed from the queue but the processPR operation will not run.

func (*Coordinator) Resume

func (a *Coordinator) Resume(_ context.Context, baseBranch *BaseBranch, prNumber int) error

Resume moves a pull request from the suspend queue to the active queue. If the pull request is not queued for updates ErrNotFound is returned.

func (*Coordinator) ResumeIfStatusPositive

func (a *Coordinator) ResumeIfStatusPositive(ctx context.Context, owner, repo string, branchNames []string)

ResumeIfStatusPositive schedules processPR for all PRs of branchNames that have a positive ready-to-merge status.

func (*Coordinator) ResumeQueue

func (a *Coordinator) ResumeQueue(baseBranch *BranchID)

Resumes the merge queue. Events will be processed again, the processPR operation is run for the first PR in the queue.

func (*Coordinator) SetPRStaleSinceIfNewer

func (a *Coordinator) SetPRStaleSinceIfNewer(
	_ context.Context,
	baseBranch *BaseBranch,
	prNumber int,
	updatedAt time.Time,
) error

SetPRStaleSinceIfNewer sets the staleSince timestamp of the PR to updatedAt, if it is newer then the current staleSince timestamp. If the PR is not queued for autoupdates, ErrNotFound is returned.

func (*Coordinator) SetPRStaleSinceIfNewerByBranch

func (a *Coordinator) SetPRStaleSinceIfNewerByBranch(
	_ context.Context,
	owner, repo string,
	branchNames []string,
	updatedAt time.Time,
) (notFound []string)

SetPRStaleSinceIfNewerByBranch sets the staleSince timestamp of the PRs for the given branch names to updatdAt, if it is newer then the current staleSince timestamp. The method returns a list of branch names for that no queued PR could be found.

func (*Coordinator) SetPullRequestPriorities

func (a *Coordinator) SetPullRequestPriorities(priorities *PRPriorityUpdates)

func (*Coordinator) Start

func (a *Coordinator) Start()

Start starts a go-routine that consumes and processes github webhook events.

func (*Coordinator) Stop

func (a *Coordinator) Stop()

Stop stops the event loop and waits until it terminates. All queues will be deleted, operations that are in progress are canceled.

func (*Coordinator) TriggerProcessPRIfFirst

func (a *Coordinator) TriggerProcessPRIfFirst(
	ctx context.Context,
	baseBranch *BaseBranch,
	prSpec PRSpecifier,
	task Task,
) (*PullRequest, error)

TriggerProcessPRIfFirst schedules the processPR operation for the first pull request in the queue if it matches prSpec. If an update was triggered, the PullRequest is returned. If the first PR does not match prSpec, ErrNotFound is returned.

func (*Coordinator) TriggerProcessPRIfFirstAllQueues

func (a *Coordinator) TriggerProcessPRIfFirstAllQueues(
	ctx context.Context,
	repoOwner string,
	repo string,
	prSpec PRSpecifier,
) (*PullRequest, error)

TriggerProcessPRIfFirstAllQueues does the same then _triggerUpdateIfFirst but does not require to specify the base branch name.

type DryCIClient

type DryCIClient struct {
	// contains filtered or unexported fields
}

func NewDryCIClient

func NewDryCIClient(logger *zap.Logger) *DryCIClient

func (*DryCIClient) Build

func (c *DryCIClient) Build(_ context.Context, job *jenkins.Job) (int64, error)

func (*DryCIClient) GetBuildFromQueueItemID

func (c *DryCIClient) GetBuildFromQueueItemID(ctx context.Context, id int64) (*jenkins.Build, error)

func (*DryCIClient) GetBuildURL

func (c *DryCIClient) GetBuildURL(_ context.Context, id int64) (string, error)

func (*DryCIClient) String

func (c *DryCIClient) String() string

type DryGithubClient

type DryGithubClient struct {
	// contains filtered or unexported fields
}

DryGithubClient is a github-client that does not do any changes on github. All operations that could cause a change are simulated and always succeed. All all other operations are forwarded to a wrapped GithubClient.

func NewDryGithubClient

func NewDryGithubClient(clt GithubClient, logger *zap.Logger) *DryGithubClient

func (*DryGithubClient) AddLabel

func (c *DryGithubClient) AddLabel(_ context.Context, owner, repo string, pullRequestOrIssueNumber int, label string) error

func (*DryGithubClient) CreateCommitStatus

func (c *DryGithubClient) CreateCommitStatus(_ context.Context, owner, repo, commit string, state githubclt.StatusState, description, context string) error

func (*DryGithubClient) CreateHeadCommitStatus

func (c *DryGithubClient) CreateHeadCommitStatus(_ context.Context, owner, repo string, pullRequestNumber int, state githubclt.StatusState, description, context string) error

func (*DryGithubClient) CreateIssueComment

func (c *DryGithubClient) CreateIssueComment(context.Context, string, string, int, string) error

func (*DryGithubClient) ListPRs

func (c *DryGithubClient) ListPRs(ctx context.Context, owner, repo string) iter.Seq2[*githubclt.PR, error]

func (*DryGithubClient) ReadyForMerge

func (*DryGithubClient) RemoveLabel

func (c *DryGithubClient) RemoveLabel(_ context.Context, owner, repo string, pullRequestOrIssueNumber int, label string) error

func (*DryGithubClient) UpdateBranch

type GithubClient

type GithubClient interface {
	AddLabel(ctx context.Context, owner, repo string, pullRequestOrIssueNumber int, label string) error
	CreateCommitStatus(ctx context.Context, owner, repo, commit string, state githubclt.StatusState, description, context string) error
	CreateHeadCommitStatus(ctx context.Context, owner, repo string, pullRequestNumber int, state githubclt.StatusState, description, context string) error
	CreateIssueComment(ctx context.Context, owner, repo string, issueOrPRNr int, comment string) error
	ListPRs(ctx context.Context, owner, repo string) iter.Seq2[*githubclt.PR, error]
	ReadyForMerge(ctx context.Context, owner, repo string, prNumber int) (*githubclt.ReadyForMergeStatus, error)
	RemoveLabel(ctx context.Context, owner, repo string, pullRequestOrIssueNumber int, label string) error
	UpdateBranch(ctx context.Context, owner, repo string, pullRequestNumber int) (*githubclt.UpdateBranchResult, error)
}

type HTTPService

type HTTPService struct {
	// contains filtered or unexported fields
}

func NewHTTPService

func NewHTTPService(autoupdater *Coordinator, endpoint string) *HTTPService

func (*HTTPService) HandlerListFunc

func (h *HTTPService) HandlerListFunc(respWr http.ResponseWriter, _ *http.Request)

func (*HTTPService) HandlerPriorityUpdate

func (h *HTTPService) HandlerPriorityUpdate(resp http.ResponseWriter, req *http.Request)

func (*HTTPService) HandlerStaticFiles

func (h *HTTPService) HandlerStaticFiles() http.Handler

func (*HTTPService) HandlerSuspendResume

func (h *HTTPService) HandlerSuspendResume(resp http.ResponseWriter, req *http.Request)

func (*HTTPService) RegisterHandlers

func (h *HTTPService) RegisterHandlers(mux *http.ServeMux)

type JenkinsClient

type JenkinsClient interface {
	fmt.Stringer
	Build(context.Context, *jenkins.Job) (int64, error)
	GetBuildFromQueueItemID(context.Context, int64) (*jenkins.Build, error)
}

type PRBranch

type PRBranch struct {
	BranchName string
}

func (*PRBranch) LogField

func (p *PRBranch) LogField() zap.Field

func (*PRBranch) String

func (p *PRBranch) String() string

func (*PRBranch) Type

func (p *PRBranch) Type() PRSpecifierType

type PRNumber

type PRNumber struct {
	Number int
}

func (*PRNumber) LogField

func (p *PRNumber) LogField() zap.Field

func (*PRNumber) String

func (p *PRNumber) String() string

func (*PRNumber) Type

func (p *PRNumber) Type() PRSpecifierType

type PRPriorityUpdate

type PRPriorityUpdate struct {
	PRNumber int
	Priority int32
}

type PRPriorityUpdates

type PRPriorityUpdates struct {
	BranchID BranchID
	Updates  []*PRPriorityUpdate
}

type PRSpecifier

type PRSpecifier interface {
	Type() PRSpecifierType
	String() string
	LogField() zap.Field
}

type PRSpecifierType

type PRSpecifierType uint8
const (
	PRSpecifierTypeUndefined PRSpecifierType = iota
	PullRequestNumber
	PullRequestBranch
)

type PullRequest

type PullRequest struct {
	Number    int
	Branch    string
	Author    string
	Title     string
	Link      string
	LogFields []zap.Field

	// EnqueuedAt is the time when the PR is added to merge-queue
	EnqueuedAt time.Time

	Priority atomic.Int32

	// SuspendCount is increased whenever a PR is moved from the active to
	// the suspend queue.
	SuspendCount atomic.Uint32

	GithubStatusLastSetState ReportedStatusState
	GithubStatusLock         sync.Mutex
	// contains filtered or unexported fields
}

func NewPullRequest

func NewPullRequest(nr int, branch, author, title, link string) (*PullRequest, error)

func NewPullRequestFromEvent

func NewPullRequestFromEvent(ev *github.PullRequest) (*PullRequest, error)

func (*PullRequest) Equal

func (p *PullRequest) Equal(other *PullRequest) bool

Equal returns true if p and other are of type PullRequest and its Number field contains the same value.

func (*PullRequest) GetLastStartedCIBuilds

func (p *PullRequest) GetLastStartedCIBuilds() map[string]*jenkins.Build

func (*PullRequest) GetStateUnchangedSince

func (p *PullRequest) GetStateUnchangedSince() time.Time

func (*PullRequest) InActiveQueueSince

func (p *PullRequest) InActiveQueueSince() time.Time

InActiveQueueSince returns since when the PR has been in the active queue. If it isn't in the active queue the zero value is returned.

func (*PullRequest) SetInActiveQueueSince

func (p *PullRequest) SetInActiveQueueSince()

SetInActiveQueueSince sets the inActiveQueueSince to [time.Now()].

func (*PullRequest) SetLastStartedCIBuilds

func (p *PullRequest) SetLastStartedCIBuilds(m map[string]*jenkins.Build)

func (*PullRequest) SetStateUnchangedSince

func (p *PullRequest) SetStateUnchangedSince(t time.Time)

func (*PullRequest) SetStateUnchangedSinceIfNewer

func (p *PullRequest) SetStateUnchangedSinceIfNewer(t time.Time)

func (*PullRequest) SetStateUnchangedSinceIfZero

func (p *PullRequest) SetStateUnchangedSinceIfZero(t time.Time)

type ReportedStatusState

type ReportedStatusState struct {
	Commit string
	State  githubclt.StatusState
}

type Repository

type Repository struct {
	OwnerLogin     string
	RepositoryName string
}

Repository is a identifies uniquely a GitHub Repository.

func (*Repository) String

func (r *Repository) String() string

type Task

type Task uint8
const (
	TaskNone Task = iota
	TaskTriggerCI
)

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.
pages

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL