issues

package
v1.0.5 Latest Latest
Warning

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

Go to latest
Published: Apr 22, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package issues implements GitLab issue operations including create, get, list, update, and delete. It exposes typed input/output structs and handler functions that interact with the GitLab Issues API v4.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Delete

func Delete(ctx context.Context, client *gitlabclient.Client, input DeleteInput) error

Delete permanently removes an issue from a GitLab project. Returns an error if the issue does not exist or the user lacks permission.

func FormatListAllMarkdown

func FormatListAllMarkdown(out ListOutput) string

FormatListAllMarkdown renders a list of globally-scoped issues as a Markdown table.

func FormatListGroupMarkdown

func FormatListGroupMarkdown(out ListGroupOutput) string

FormatListGroupMarkdown renders a paginated list of group issues as a Markdown table.

func FormatListMarkdown

func FormatListMarkdown(out ListOutput) string

FormatListMarkdown renders a list of issues as a Markdown table.

func FormatMarkdown

func FormatMarkdown(i Output) string

FormatMarkdown renders a single issue as a Markdown summary.

func FormatParticipantsMarkdown

func FormatParticipantsMarkdown(out ParticipantsOutput) string

FormatParticipantsMarkdown renders an issue's participant list as Markdown.

func FormatRelatedMRsMarkdown

func FormatRelatedMRsMarkdown(out RelatedMRsOutput, heading string) string

FormatRelatedMRsMarkdown renders a list of related merge requests as Markdown.

func FormatTimeStatsMarkdown

func FormatTimeStatsMarkdown(ts TimeStatsOutput) string

FormatTimeStatsMarkdown renders time tracking statistics as Markdown.

func FormatTodoMarkdown

func FormatTodoMarkdown(t TodoOutput) string

FormatTodoMarkdown renders a to-do item as a Markdown summary.

func RegisterTools

func RegisterTools(server *mcp.Server, client *gitlabclient.Client)

RegisterTools registers CRUD tools for GitLab issues.

Types

type AddSpentTimeInput

type AddSpentTimeInput struct {
	ProjectID toolutil.StringOrInt `json:"project_id" jsonschema:"Project ID or URL-encoded path,required"`
	IssueIID  int64                `json:"issue_iid"  jsonschema:"Issue internal ID,required"`
	Duration  string               `json:"duration"   jsonschema:"Human-readable duration (e.g. 1h, 30m, 1w2d),required"`
	Summary   string               `json:"summary,omitempty" jsonschema:"Optional summary of work done"`
}

AddSpentTimeInput defines parameters for adding spent time to an issue.

type CreateInput

type CreateInput struct {
	// Basic metadata
	ProjectID   toolutil.StringOrInt `json:"project_id" jsonschema:"Project ID or URL-encoded path,required"`
	Title       string               `json:"title" jsonschema:"Issue title,required"`
	Description string               `json:"description,omitempty" jsonschema:"Issue description (Markdown supported)"`
	IssueType   string               `json:"issue_type,omitempty" jsonschema:"Issue type (issue, incident, test_case, task)"`

	// Assignment and tracking
	AssigneeID  int64   `json:"assignee_id,omitempty" jsonschema:"Single user ID to assign (use assignee_ids for multiple)"`
	AssigneeIDs []int64 `json:"assignee_ids,omitempty" jsonschema:"User IDs to assign"`
	Labels      string  `json:"labels,omitempty" jsonschema:"Comma-separated labels to apply"`
	MilestoneID int64   `json:"milestone_id,omitempty" jsonschema:"Milestone ID to associate,required"`
	EpicID      int64   `json:"epic_id,omitempty" jsonschema:"Epic ID to associate the issue with"`
	Weight      int64   `json:"weight,omitempty" jsonschema:"Issue weight (0 or higher)"`
	DueDate     string  `json:"due_date,omitempty" jsonschema:"Due date in YYYY-MM-DD format"`

	// Behavior flags
	Confidential *bool  `json:"confidential,omitempty" jsonschema:"Mark issue as confidential"`
	CreatedAt    string `json:"created_at,omitempty" jsonschema:"Creation date override (ISO 8601, requires admin permissions)"`

	// Discussion resolution
	MergeRequestToResolveDiscussionsOf int64  `json:"merge_request_to_resolve_discussions_of,omitempty" jsonschema:"MR IID whose unresolved discussions become issues"`
	DiscussionToResolve                string `json:"discussion_to_resolve,omitempty" jsonschema:"Discussion ID to mark as resolved by this issue"`
}

CreateInput defines parameters for creating a new issue.

type CreateTodoInput

type CreateTodoInput struct {
	ProjectID toolutil.StringOrInt `json:"project_id" jsonschema:"Project ID or URL-encoded path,required"`
	IssueIID  int64                `json:"issue_iid"  jsonschema:"Issue internal ID,required"`
}

CreateTodoInput defines parameters for creating a to-do for an issue.

type DeleteInput

type DeleteInput struct {
	ProjectID toolutil.StringOrInt `json:"project_id" jsonschema:"Project ID or URL-encoded path,required"`
	IssueIID  int64                `json:"issue_iid"  jsonschema:"Issue IID (project-scoped internal ID, not 'issue_id'),required"`
}

DeleteInput defines parameters for deleting an issue.

type GetByIDInput

type GetByIDInput struct {
	IssueID int64 `json:"issue_id" jsonschema:"The global issue ID (not the project-scoped IID),required"`
}

GetByIDInput defines parameters for retrieving an issue by its global ID.

type GetInput

type GetInput struct {
	ProjectID toolutil.StringOrInt `json:"project_id" jsonschema:"Project ID or URL-encoded path,required"`
	IssueIID  int64                `json:"issue_iid"  jsonschema:"Issue IID (project-scoped internal ID, not 'issue_id'),required"`
}

GetInput defines parameters for retrieving a single issue.

type ListAllInput

type ListAllInput struct {
	State            string `json:"state,omitempty"             jsonschema:"Filter by state (opened, closed, all)"`
	Labels           string `json:"labels,omitempty"            jsonschema:"Comma-separated label names to filter by"`
	Milestone        string `json:"milestone,omitempty"         jsonschema:"Milestone title to filter by"`
	Scope            string `json:"scope,omitempty"             jsonschema:"Filter by scope (created_by_me, assigned_to_me, all)"`
	Search           string `json:"search,omitempty"            jsonschema:"Search in title and description"`
	AssigneeUsername string `json:"assignee_username,omitempty" jsonschema:"Filter by assignee username"`
	AuthorUsername   string `json:"author_username,omitempty"   jsonschema:"Filter by author username"`
	OrderBy          string `json:"order_by,omitempty"          jsonschema:"Order by field (created_at, updated_at, priority, due_date)"`
	Sort             string `json:"sort,omitempty"              jsonschema:"Sort direction (asc, desc)"`
	CreatedAfter     string `json:"created_after,omitempty"     jsonschema:"Return issues created after date (ISO 8601)"`
	CreatedBefore    string `json:"created_before,omitempty"    jsonschema:"Return issues created before date (ISO 8601)"`
	UpdatedAfter     string `json:"updated_after,omitempty"     jsonschema:"Return issues updated after date (ISO 8601)"`
	UpdatedBefore    string `json:"updated_before,omitempty"    jsonschema:"Return issues updated before date (ISO 8601)"`
	Confidential     *bool  `json:"confidential,omitempty"      jsonschema:"Filter by confidential status"`
	toolutil.PaginationInput
}

ListAllInput defines parameters for the global ListIssues endpoint (no project scope).

type ListGroupInput

type ListGroupInput struct {
	GroupID        toolutil.StringOrInt `json:"group_id"                jsonschema:"Group ID or URL-encoded path,required"`
	State          string               `json:"state,omitempty"         jsonschema:"Filter by state (opened, closed, all)"`
	Labels         string               `json:"labels,omitempty"        jsonschema:"Comma-separated list of labels to filter by"`
	Milestone      string               `json:"milestone,omitempty"     jsonschema:"Milestone title to filter by"`
	Search         string               `json:"search,omitempty"        jsonschema:"Search in title and description"`
	Scope          string               `json:"scope,omitempty"         jsonschema:"Scope (created_by_me, assigned_to_me, all)"`
	AuthorUsername string               `json:"author_username,omitempty" jsonschema:"Filter by author username"`
	CreatedAfter   string               `json:"created_after,omitempty"  jsonschema:"Return issues created after date (ISO 8601)"`
	CreatedBefore  string               `json:"created_before,omitempty" jsonschema:"Return issues created before date (ISO 8601)"`
	UpdatedAfter   string               `json:"updated_after,omitempty"  jsonschema:"Return issues updated after date (ISO 8601)"`
	UpdatedBefore  string               `json:"updated_before,omitempty" jsonschema:"Return issues updated before date (ISO 8601)"`
	toolutil.PaginationInput
}

ListGroupInput defines parameters for listing issues across a group.

type ListGroupOutput

type ListGroupOutput struct {
	toolutil.HintableOutput
	Issues     []Output                  `json:"issues"`
	Pagination toolutil.PaginationOutput `json:"pagination"`
}

ListGroupOutput holds a paginated list of group issues.

func ListGroup

func ListGroup(ctx context.Context, client *gitlabclient.Client, input ListGroupInput) (ListGroupOutput, error)

ListGroup retrieves a paginated list of issues across all projects in a group.

type ListInput

type ListInput struct {
	ProjectID        toolutil.StringOrInt `json:"project_id"                  jsonschema:"Project ID or URL-encoded path,required"`
	State            string               `json:"state,omitempty"             jsonschema:"Filter by state (opened, closed, all)"`
	Labels           string               `json:"labels,omitempty"            jsonschema:"Comma-separated label names to filter by"`
	NotLabels        string               `json:"not_labels,omitempty"        jsonschema:"Comma-separated label names to exclude"`
	Milestone        string               `json:"milestone,omitempty"         jsonschema:"Milestone title to filter by"`
	Scope            string               `json:"scope,omitempty"             jsonschema:"Filter by scope (created_by_me, assigned_to_me, all)"`
	Search           string               `json:"search,omitempty"            jsonschema:"Search in title and description"`
	AssigneeUsername string               `json:"assignee_username,omitempty" jsonschema:"Filter by assignee username"`
	AuthorUsername   string               `json:"author_username,omitempty"   jsonschema:"Filter by author username"`
	IIDs             []int64              `json:"iids,omitempty"              jsonschema:"Filter by issue internal IDs"`
	IssueType        string               `json:"issue_type,omitempty"        jsonschema:"Filter by issue type (issue, incident, test_case, task)"`
	Confidential     *bool                `json:"confidential,omitempty"      jsonschema:"Filter by confidential status"`
	CreatedAfter     string               `json:"created_after,omitempty"     jsonschema:"Return issues created after date (ISO 8601 format, e.g. 2025-01-01T00:00:00Z)"`
	CreatedBefore    string               `` /* 126-byte string literal not displayed */
	UpdatedAfter     string               `json:"updated_after,omitempty"     jsonschema:"Return issues updated after date (ISO 8601 format, e.g. 2025-01-01T00:00:00Z)"`
	UpdatedBefore    string               `` /* 126-byte string literal not displayed */
	OrderBy          string               `json:"order_by,omitempty"          jsonschema:"Order by field (created_at, updated_at, priority, due_date)"`
	Sort             string               `json:"sort,omitempty"              jsonschema:"Sort direction (asc, desc)"`
	toolutil.PaginationInput
}

ListInput defines filters for listing project issues.

type ListMRsClosingInput

type ListMRsClosingInput struct {
	ProjectID toolutil.StringOrInt `json:"project_id" jsonschema:"Project ID or URL-encoded path,required"`
	IssueIID  int64                `json:"issue_iid"  jsonschema:"Issue internal ID,required"`
	toolutil.PaginationInput
}

ListMRsClosingInput defines parameters for listing MRs that close an issue on merge.

type ListMRsRelatedInput

type ListMRsRelatedInput struct {
	ProjectID toolutil.StringOrInt `json:"project_id" jsonschema:"Project ID or URL-encoded path,required"`
	IssueIID  int64                `json:"issue_iid"  jsonschema:"Issue internal ID,required"`
	toolutil.PaginationInput
}

ListMRsRelatedInput defines parameters for listing MRs related to an issue.

type ListOutput

type ListOutput struct {
	toolutil.HintableOutput
	Issues     []Output                  `json:"issues"`
	Pagination toolutil.PaginationOutput `json:"pagination"`
}

ListOutput holds a paginated list of issues.

func List

func List(ctx context.Context, client *gitlabclient.Client, input ListInput) (ListOutput, error)

List retrieves a paginated list of issues for a GitLab project. Supports filtering by state, labels, milestone, search text, assignee, author, and sorting options. Returns the issues with pagination metadata.

func ListAll

func ListAll(ctx context.Context, client *gitlabclient.Client, input ListAllInput) (ListOutput, error)

ListAll retrieves a paginated list of issues visible to the authenticated user across all projects (global scope).

type MoveInput

type MoveInput struct {
	ProjectID   toolutil.StringOrInt `json:"project_id"     jsonschema:"Source project ID or URL-encoded path,required"`
	IssueIID    int64                `json:"issue_iid"      jsonschema:"Issue internal ID,required"`
	ToProjectID int64                `json:"to_project_id"  jsonschema:"Target project ID to move issue to,required"`
}

MoveInput defines parameters for moving an issue to another project.

type Output

type Output struct {
	toolutil.HintableOutput
	ID                  int64    `json:"id"`
	IID                 int64    `json:"issue_iid"`
	Title               string   `json:"title"`
	Description         string   `json:"description"`
	State               string   `json:"state"`
	Labels              []string `json:"labels"`
	Assignees           []string `json:"assignees"`
	Milestone           string   `json:"milestone"`
	Author              string   `json:"author"`
	ClosedBy            string   `json:"closed_by,omitempty"`
	WebURL              string   `json:"web_url"`
	CreatedAt           string   `json:"created_at"`
	UpdatedAt           string   `json:"updated_at"`
	ClosedAt            string   `json:"closed_at"`
	DueDate             string   `json:"due_date"`
	Confidential        bool     `json:"confidential"`
	DiscussionLocked    bool     `json:"discussion_locked"`
	ProjectID           int64    `json:"project_id"`
	Weight              int64    `json:"weight,omitempty"`
	IssueType           string   `json:"issue_type,omitempty"`
	HealthStatus        string   `json:"health_status,omitempty"`
	References          string   `json:"references,omitempty"`
	MergeRequestCount   int64    `json:"merge_request_count,omitempty"`
	TaskCompletionCount int64    `json:"task_completion_count,omitempty"`
	TaskCompletionTotal int64    `json:"task_completion_total,omitempty"`
	UserNotesCount      int64    `json:"user_notes_count,omitempty"`
	Upvotes             int64    `json:"upvotes,omitempty"`
	Downvotes           int64    `json:"downvotes,omitempty"`
	Subscribed          bool     `json:"subscribed"`
	TimeEstimate        int64    `json:"time_estimate,omitempty"`
	TotalTimeSpent      int64    `json:"total_time_spent,omitempty"`
	MovedToID           int64    `json:"moved_to_id,omitempty"`
	EpicIssueID         int64    `json:"epic_issue_id,omitempty"`
}

Output represents a GitLab issue.

func Create

func Create(ctx context.Context, client *gitlabclient.Client, input CreateInput) (Output, error)

Create creates a new issue in the specified GitLab project. It maps all optional fields (description, labels, assignees, milestone, due date, confidential) to the GitLab API request. Returns the created issue or an error if the API call fails.

func Get

func Get(ctx context.Context, client *gitlabclient.Client, input GetInput) (Output, error)

Get retrieves a single issue by its internal ID from a GitLab project. Returns the issue details or an error if the issue is not found.

func GetByID

func GetByID(ctx context.Context, client *gitlabclient.Client, input GetByIDInput) (Output, error)

GetByID retrieves a single issue by its global numeric ID.

func Move

func Move(ctx context.Context, client *gitlabclient.Client, input MoveInput) (Output, error)

Move moves an issue from one project to another.

func Reorder

func Reorder(ctx context.Context, client *gitlabclient.Client, input ReorderInput) (Output, error)

Reorder changes the position of an issue relative to other issues.

func Subscribe

func Subscribe(ctx context.Context, client *gitlabclient.Client, input SubscribeInput) (Output, error)

Subscribe subscribes the authenticated user to an issue for notifications.

func ToOutput

func ToOutput(issue *gl.Issue) Output

ToOutput converts a GitLab API gl.Issue to the MCP tool output format, extracting author, milestone, assignees, and formatting timestamps as RFC 3339 strings. Nil labels are normalized to an empty slice.

func Unsubscribe

func Unsubscribe(ctx context.Context, client *gitlabclient.Client, input UnsubscribeInput) (Output, error)

Unsubscribe removes the authenticated user's subscription from an issue.

func Update

func Update(ctx context.Context, client *gitlabclient.Client, input UpdateInput) (Output, error)

Update modifies an existing issue in a GitLab project. Only non-zero fields in the input are applied. Supports changing title, description, state, assignees, labels (replace, add, remove), milestone, due date, and confidential flag. Returns the updated issue.

type ParticipantOutput

type ParticipantOutput struct {
	ID       int64  `json:"id"`
	Username string `json:"username"`
	Name     string `json:"name"`
	WebURL   string `json:"web_url"`
}

ParticipantOutput represents a participant in an issue.

type ParticipantsOutput

type ParticipantsOutput struct {
	toolutil.HintableOutput
	Participants []ParticipantOutput `json:"participants"`
}

ParticipantsOutput holds a list of issue participants.

func GetParticipants

func GetParticipants(ctx context.Context, client *gitlabclient.Client, input GetInput) (ParticipantsOutput, error)

GetParticipants retrieves the list of participants in an issue.

type RelatedMROutput

type RelatedMROutput struct {
	ID           int64  `json:"id"`
	IID          int64  `json:"mr_iid"`
	Title        string `json:"title"`
	State        string `json:"state"`
	SourceBranch string `json:"source_branch"`
	TargetBranch string `json:"target_branch"`
	Author       string `json:"author"`
	WebURL       string `json:"web_url"`
}

RelatedMROutput represents a basic merge request linked to an issue.

type RelatedMRsOutput

type RelatedMRsOutput struct {
	toolutil.HintableOutput
	MergeRequests []RelatedMROutput         `json:"merge_requests"`
	Pagination    toolutil.PaginationOutput `json:"pagination"`
}

RelatedMRsOutput holds a paginated list of merge requests related to an issue.

func ListMRsClosing

func ListMRsClosing(ctx context.Context, client *gitlabclient.Client, input ListMRsClosingInput) (RelatedMRsOutput, error)

ListMRsClosing retrieves merge requests that will close this issue on merge.

func ListMRsRelated

func ListMRsRelated(ctx context.Context, client *gitlabclient.Client, input ListMRsRelatedInput) (RelatedMRsOutput, error)

ListMRsRelated retrieves merge requests related to this issue.

type ReorderInput

type ReorderInput struct {
	ProjectID    toolutil.StringOrInt `json:"project_id"              jsonschema:"Project ID or URL-encoded path,required"`
	IssueIID     int64                `json:"issue_iid"               jsonschema:"Issue internal ID,required"`
	MoveAfterID  *int64               `json:"move_after_id,omitempty"  jsonschema:"ID of issue to position after"`
	MoveBeforeID *int64               `json:"move_before_id,omitempty" jsonschema:"ID of issue to position before"`
}

ReorderInput defines parameters for reordering an issue.

type SetTimeEstimateInput

type SetTimeEstimateInput struct {
	ProjectID toolutil.StringOrInt `json:"project_id" jsonschema:"Project ID or URL-encoded path,required"`
	IssueIID  int64                `json:"issue_iid"  jsonschema:"Issue internal ID,required"`
	Duration  string               `json:"duration"   jsonschema:"Human-readable duration (e.g. 3h30m, 1w2d),required"`
}

SetTimeEstimateInput defines parameters for setting a time estimate on an issue.

type SubscribeInput

type SubscribeInput struct {
	ProjectID toolutil.StringOrInt `json:"project_id" jsonschema:"Project ID or URL-encoded path,required"`
	IssueIID  int64                `json:"issue_iid"  jsonschema:"Issue internal ID,required"`
}

SubscribeInput defines parameters for subscribing to an issue.

type TimeStatsOutput

type TimeStatsOutput struct {
	toolutil.HintableOutput
	HumanTimeEstimate   string `json:"human_time_estimate"`
	HumanTotalTimeSpent string `json:"human_total_time_spent"`
	TimeEstimate        int64  `json:"time_estimate"`
	TotalTimeSpent      int64  `json:"total_time_spent"`
}

TimeStatsOutput represents time tracking statistics for an issue.

func AddSpentTime

func AddSpentTime(ctx context.Context, client *gitlabclient.Client, input AddSpentTimeInput) (TimeStatsOutput, error)

AddSpentTime adds spent time for an issue.

func GetTimeStats

func GetTimeStats(ctx context.Context, client *gitlabclient.Client, input GetInput) (TimeStatsOutput, error)

GetTimeStats retrieves total time tracking statistics for an issue.

func ResetSpentTime

func ResetSpentTime(ctx context.Context, client *gitlabclient.Client, input GetInput) (TimeStatsOutput, error)

ResetSpentTime resets the total spent time for an issue.

func ResetTimeEstimate

func ResetTimeEstimate(ctx context.Context, client *gitlabclient.Client, input GetInput) (TimeStatsOutput, error)

ResetTimeEstimate resets the time estimate for an issue back to zero.

func SetTimeEstimate

func SetTimeEstimate(ctx context.Context, client *gitlabclient.Client, input SetTimeEstimateInput) (TimeStatsOutput, error)

SetTimeEstimate sets the time estimate for an issue.

type TodoOutput

type TodoOutput struct {
	toolutil.HintableOutput
	ID          int64  `json:"id"`
	ActionName  string `json:"action_name"`
	TargetType  string `json:"target_type"`
	TargetTitle string `json:"target_title"`
	TargetURL   string `json:"target_url"`
	Body        string `json:"body,omitempty"`
	State       string `json:"state"`
	CreatedAt   string `json:"created_at,omitempty"`
}

TodoOutput represents a to-do item created from an issue.

func CreateTodo

func CreateTodo(ctx context.Context, client *gitlabclient.Client, input CreateTodoInput) (TodoOutput, error)

CreateTodo creates a to-do item for the authenticated user on the specified issue.

type UnsubscribeInput

type UnsubscribeInput struct {
	ProjectID toolutil.StringOrInt `json:"project_id" jsonschema:"Project ID or URL-encoded path,required"`
	IssueIID  int64                `json:"issue_iid"  jsonschema:"Issue internal ID,required"`
}

UnsubscribeInput defines parameters for unsubscribing from an issue.

type UpdateInput

type UpdateInput struct {
	ProjectID        toolutil.StringOrInt `json:"project_id"              jsonschema:"Project ID or URL-encoded path,required"`
	IssueIID         int64                `json:"issue_iid"               jsonschema:"Issue IID (project-scoped internal ID, not 'issue_id'),required"`
	Title            string               `json:"title,omitempty"         jsonschema:"New title"`
	Description      string               `json:"description,omitempty"   jsonschema:"New description (Markdown supported)"`
	StateEvent       string               `json:"state_event,omitempty"   jsonschema:"State transition (close, reopen)"`
	AssigneeID       int64                `json:"assignee_id,omitempty"       jsonschema:"Single user ID to assign (use assignee_ids for multiple)"`
	AssigneeIDs      []int64              `json:"assignee_ids,omitempty"  jsonschema:"New assignee user IDs"`
	Labels           string               `json:"labels,omitempty"        jsonschema:"Comma-separated labels to replace all existing"`
	AddLabels        string               `json:"add_labels,omitempty"    jsonschema:"Comma-separated labels to add without removing existing"`
	RemoveLabels     string               `json:"remove_labels,omitempty" jsonschema:"Comma-separated labels to remove"`
	EpicID           int64                `json:"epic_id,omitempty"       jsonschema:"Epic ID to associate (EE only)"`
	MilestoneID      int64                `json:"milestone_id,omitempty"  jsonschema:"New milestone ID (0 to unset),required"`
	DueDate          string               `json:"due_date,omitempty"      jsonschema:"New due date in YYYY-MM-DD format"`
	Confidential     *bool                `json:"confidential,omitempty"  jsonschema:"Update confidential flag"`
	IssueType        string               `json:"issue_type,omitempty"    jsonschema:"Issue type (issue, incident, test_case, task)"`
	Weight           int64                `json:"weight,omitempty"        jsonschema:"Issue weight (0 or higher)"`
	DiscussionLocked *bool                `json:"discussion_locked,omitempty" jsonschema:"Lock discussions on this issue"`
}

UpdateInput defines parameters for updating an existing issue.

Jump to

Keyboard shortcuts

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