Documentation ¶
Overview ¶
Package github implements sync mechanism to mirror GitHub issue state into a storage.DB as well as code to inspect that state and to make issue changes on GitHub. All the functionality is provided by the Client, created by New.
Index ¶
- Constants
- func CleanBody(body string) string
- func CleanTitle(title string) string
- func ParseIssueURL(u string) (project string, number int64, err error)
- func Scrub(req *http.Request) error
- func Token(sdb secret.DB) string
- func ValidWebhookTestdata(t *testing.T, event WebhookEventType, payload any) (*http.Request, secret.DB)
- type Client
- func (c *Client) Add(project string) error
- func (c *Client) DocWatcher() *timed.Watcher[*Event]
- func (c *Client) DownloadIssue(ctx context.Context, url string) (*Issue, error)
- func (c *Client) DownloadIssueComment(ctx context.Context, url string) (*IssueComment, error)
- func (c *Client) EditIssue(ctx context.Context, issue *Issue, changes *IssueChanges) error
- func (c *Client) EditIssueComment(ctx context.Context, comment *IssueComment, changes *IssueCommentChanges) error
- func (c *Client) EnableTesting()
- func (c *Client) EventWatcher(name string) *timed.Watcher[*Event]
- func (c *Client) Events(project string, issueMin, issueMax int64) iter.Seq[*Event]
- func (c *Client) EventsAfter(t timed.DBTime, project string) iter.Seq[*Event]
- func (c *Client) LookupIssueURL(url string) (*Issue, error)
- func (c *Client) PostIssueComment(ctx context.Context, issue *Issue, changes *IssueCommentChanges) (id, url string, err error)
- func (c *Client) Sync(ctx context.Context) error
- func (c *Client) SyncProject(ctx context.Context, project string) (err error)
- func (c *Client) Testing() *TestingClient
- func (*Client) ToDocs(e *Event) (iter.Seq[*docs.Doc], bool)
- type Event
- type Issue
- func (x *Issue) Author() *model.Identity
- func (x *Issue) Body_() string
- func (x *Issue) CanEdit() bool
- func (x *Issue) CanHaveChildren() bool
- func (x *Issue) CreatedAt_() time.Time
- func (i *Issue) DocID() string
- func (x *Issue) ID() string
- func (x *Issue) ParentID() string
- func (x *Issue) Project() string
- func (x *Issue) Properties() map[string]any
- func (x *Issue) Title_() string
- func (x *Issue) UpdatedAt_() time.Time
- func (x *Issue) Updates() model.PostUpdates
- type IssueChanges
- type IssueComment
- func (x *IssueComment) Author() *model.Identity
- func (x *IssueComment) Body_() string
- func (x *IssueComment) CanEdit() bool
- func (x *IssueComment) CanHaveChildren() bool
- func (x *IssueComment) CommentID() int64
- func (x *IssueComment) CreatedAt_() time.Time
- func (x *IssueComment) ID() string
- func (x *IssueComment) Issue() int64
- func (x *IssueComment) ParentID() string
- func (x *IssueComment) Project() string
- func (x *IssueComment) Properties() map[string]any
- func (x *IssueComment) Title_() string
- func (x *IssueComment) UpdatedAt_() time.Time
- func (x *IssueComment) Updates() model.PostUpdates
- type IssueCommentChanges
- type IssueEvent
- type IssueOverviewResult
- type Label
- type Milestone
- type Rename
- type Repository
- type TestingClient
- func (tc *TestingClient) AddIssue(project string, issue *Issue)
- func (tc *TestingClient) AddIssueComment(project string, issue int64, comment *IssueComment) int64
- func (tc *TestingClient) AddIssueEvent(project string, issue int64, event *IssueEvent)
- func (tc *TestingClient) ClearEdits()
- func (tc *TestingClient) Edits() []*TestingEdit
- func (tc *TestingClient) LoadTxtar(file string) error
- func (tc *TestingClient) LoadTxtarData(data []byte) error
- type TestingEdit
- type User
- type WebhookEvent
- type WebhookEventType
- type WebhookIssueAction
- type WebhookIssueCommentAction
- type WebhookIssueCommentEvent
- type WebhookIssueEvent
Constants ¶
const DocWatcherID = "githubdocs"
Variables ¶
This section is empty.
Functions ¶
func CleanBody ¶
CleanBody should clean the body for indexing. For now we assume the LLM is good enough at Markdown not to bother. In the future we may want to make various changes like inlining the programs associated with playground URLs, and we may also want to remove any HTML tags from the Markdown.
func CleanTitle ¶
CleanTitle should clean the title for indexing. For now we assume the LLM is good enough at Markdown not to bother.
func ParseIssueURL ¶
ParseIssueURL expects a GitHUB API URL for an issue (for example "https://api.github.com/repos/org/r/issues/123") and returns the project (for example "org/r") and issue number.
func Scrub ¶
Scrub is a scrubber for use with rsc.io/httprr when writing tests that access GitHub through an httprr.RecordReplay. It removes auth credentials from the request.
func ValidWebhookTestdata ¶
func ValidWebhookTestdata(t *testing.T, event WebhookEventType, payload any) (*http.Request, secret.DB)
ValidWebhookTestdata returns an HTTP request and a secret DB (inputs to ValidateWebhookRequest) that will pass validation. payload is marshaled into JSON as the body of the returned request.
For testing.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
A Client is a connection to GitHub state in a database and on GitHub itself.
func New ¶
New returns a new client that uses the given logger, databases, and HTTP client.
The secret database is expected to have a secret named "api.github.com" of the form "user:pass" where user is a user-name (ignored by GitHub) and pass is an API token ("ghp_...").
func (*Client) Add ¶
Add adds a GitHub project of the form "owner/repo" (for example "golang/go") to the database. It only adds the project sync metadata. The initial data fetch does not happen until [Sync] or [SyncProject] is called. If the project is already present, Add does nothing and returns nil.
func (*Client) DocWatcher ¶
DocWatcher returns the event watcher with name "githubdocs". Implements docs.Source.DocWatcher.
func (*Client) DownloadIssue ¶
DownloadIssue downloads the current issue JSON from the given URL and decodes it into an issue. Given an issue, c.DownloadIssue(issue.URL) fetches the very latest state for the issue.
func (*Client) DownloadIssueComment ¶
DownloadIssueComment downloads the current comment JSON from the given URL and decodes it into an IssueComment. Given a comment, c.DownloadIssueComment(comment.URL) fetches the very latest state for the comment.
func (*Client) EditIssueComment ¶
func (c *Client) EditIssueComment(ctx context.Context, comment *IssueComment, changes *IssueCommentChanges) error
EditIssueComment changes the comment on GitHub to have the new body. It is typically a good idea to use c.DownloadIssueComment first and check that the live comment body matches the one obtained from the database, to minimize race windows.
func (*Client) EnableTesting ¶
func (c *Client) EnableTesting()
EnableTesting enables testing mode, in which edits are diverted and a TestingClient is available. If the program is itself a test binary (built or run using “go test”), testing mode is enabled automatically. EnableTesting can be useful in experimental programs to make sure that no edits are applied to GitHub.
func (*Client) EventWatcher ¶
EventWatcher returns a new timed.Watcher with the given name. It picks up where any previous Watcher of the same name left off.
func (*Client) Events ¶
Events returns an iterator over issue events for the given project, limited to issues in the range issueMin ≤ issue ≤ issueMax. If issueMax < 0, there is no upper limit. The events are iterated over in (Project, Issue, API, ID) order, so "/issues" events come first, then "/issues/comments", then "/issues/events". Within a specific API, the events are ordered by increasing ID, which corresponds to increasing event time on GitHub.
func (*Client) EventsAfter ¶
EventsAfter returns an iterator over events in the given project after DBTime t, which should be e.DBTime from the most recent processed event. The events are iterated over in DBTime order, so the DBTime of the last successfully processed event can be used in a future call to EventsAfter. If project is the empty string, then events from all projects are returned.
func (*Client) LookupIssueURL ¶
LookupIssueURL looks up an issue by URL (for example "https://github.com/golang/go/issues/12345"), only consulting the database (not actual GitHub).
func (*Client) PostIssueComment ¶
func (c *Client) PostIssueComment(ctx context.Context, issue *Issue, changes *IssueCommentChanges) (id, url string, err error)
PostIssueComment posts a new comment with the given body (written in Markdown) on issue. It returns an API URL for the new comment, and a URL suitable for display.
func (*Client) SyncProject ¶
SyncProject syncs a single project.
func (*Client) Testing ¶
func (c *Client) Testing() *TestingClient
Testing returns a TestingClient, which provides access to Client functionality intended for testing. Testing only returns a non-nil TestingClient in testing mode, which is active if the current program is a test binary (that is, testing.Testing returns true) or if Client.EnableTesting has been called. Otherwise, Testing returns nil.
Each Client has only one TestingClient associated with it. Every call to Testing returns the same TestingClient.
type Event ¶
type Event struct { DBTime timed.DBTime // when event was last written Project string // project ("golang/go") Issue int64 // issue number API string // API endpoint for event: "/issues", "/issues/comments", or "/issues/events" ID int64 // ID of event; each API has a different ID space. (Project, Issue, API, ID) is assumed unique JSON []byte // JSON for the event data Typed any // Typed unmarshaling of the event data, of type *Issue, *IssueComment, or *IssueEvent }
An Event is a single GitHub issue event stored in the database.
func (*Event) LastWritten ¶
LastWritten implements docs.Entry.LastWritten.
type Issue ¶
type Issue struct { URL string `json:"url"` HTMLURL string `json:"html_url"` Number int64 `json:"number"` User User `json:"user"` Title string `json:"title"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` ClosedAt string `json:"closed_at"` Body string `json:"body"` Assignees []User `json:"assignees"` Milestone Milestone `json:"milestone"` State string `json:"state"` PullRequest *struct{} `json:"pull_request"` Locked bool `json:"locked"` ActiveLockReason string `json:"active_lock_reason"` Labels []Label `json:"labels"` }
Issue is the GitHub JSON structure for an issue creation event.
func LookupIssue ¶
LookupIssue looks up an issue by project and issue number (for example "golang/go", 12345), only consulting the database (not actual GitHub).
func (*Issue) CanHaveChildren ¶
func (*Issue) CreatedAt_ ¶
func (*Issue) DocID ¶
DocID returns the ID of this issue for storage in a docs.Corpus or a storage.VectorDB.
func (*Issue) Properties ¶
func (*Issue) UpdatedAt_ ¶
func (*Issue) Updates ¶
func (x *Issue) Updates() model.PostUpdates
type IssueChanges ¶
type IssueChanges struct { Title string `json:"title,omitempty"` Body string `json:"body,omitempty"` State string `json:"state,omitempty"` Labels *[]string `json:"labels,omitempty"` }
An IssueChanges specifies changes to make to an issue. Fields that are the empty string or a nil pointer are ignored.
Note that Labels is the new set of all labels for the issue, not labels to add. If you are adding a single label, you need to include all the existing labels as well. Labels is a *[]string so that it can be set to new([]string) to clear the labels.
func (*IssueChanges) SetBody ¶
func (ch *IssueChanges) SetBody(s string) error
func (*IssueChanges) SetTitle ¶
func (ch *IssueChanges) SetTitle(s string) error
type IssueComment ¶
type IssueComment struct { URL string `json:"url"` IssueURL string `json:"issue_url"` HTMLURL string `json:"html_url"` User User `json:"user"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` Body string `json:"body"` }
IssueComment is the GitHub JSON structure for an issue comment event.
func (*IssueComment) Author ¶
func (x *IssueComment) Author() *model.Identity
func (*IssueComment) Body_ ¶
func (x *IssueComment) Body_() string
func (*IssueComment) CanEdit ¶
func (x *IssueComment) CanEdit() bool
func (*IssueComment) CanHaveChildren ¶
func (x *IssueComment) CanHaveChildren() bool
func (*IssueComment) CommentID ¶
func (x *IssueComment) CommentID() int64
CommentID returns the issue comment's numeric ID. The ID appears to be unique across all comments on GitHub, but we only assume it is unique within a single issue.
func (*IssueComment) CreatedAt_ ¶
func (x *IssueComment) CreatedAt_() time.Time
func (*IssueComment) Issue ¶
func (x *IssueComment) Issue() int64
Issue returns the issue comment's issue number.
func (*IssueComment) ParentID ¶
func (x *IssueComment) ParentID() string
func (*IssueComment) Project ¶
func (x *IssueComment) Project() string
Project returns the issue comment's GitHub project (for example, "golang/go").
func (*IssueComment) Properties ¶
func (x *IssueComment) Properties() map[string]any
func (*IssueComment) Title_ ¶
func (x *IssueComment) Title_() string
func (*IssueComment) UpdatedAt_ ¶
func (x *IssueComment) UpdatedAt_() time.Time
func (*IssueComment) Updates ¶
func (x *IssueComment) Updates() model.PostUpdates
type IssueCommentChanges ¶
type IssueCommentChanges struct {
Body string `json:"body,omitempty"`
}
func (*IssueCommentChanges) SetBody ¶
func (ch *IssueCommentChanges) SetBody(s string) error
func (*IssueCommentChanges) SetTitle ¶
func (ch *IssueCommentChanges) SetTitle(string) error
type IssueEvent ¶
type IssueEvent struct { // NOTE: Issue field is not present when downloading for a specific issue, // only in the master feed for the whole repo. So do not add it here. ID int64 `json:"id"` URL string `json:"url"` Actor User `json:"actor"` Event string `json:"event"` Labels []Label `json:"labels"` LockReason string `json:"lock_reason"` CreatedAt string `json:"created_at"` CommitID string `json:"commit_id"` Assigner User `json:"assigner"` Assignees []User `json:"assignees"` Milestone Milestone `json:"milestone"` Rename Rename `json:"rename"` }
IssueEvent is the GitHub JSON structure for an issue metadata event.
type IssueOverviewResult ¶
type IssueOverviewResult struct { *Issue // the issue itself NewComments int // number of new comments for this issue (for [CommentsAfterOverview]) TotalComments int // number of comments for this issue Overview *llmapp.OverviewResult // the LLM-generated issue and comment summary }
IssueOverviewResult is the result of IssueOverview or UpdateOverview. It contains the generated overview and metadata about the issue.
func IssueOverview ¶
func IssueOverview(ctx context.Context, lc *llmapp.Client, db storage.DB, project string, issue int64) (*IssueOverviewResult, error)
IssueOverview returns an LLM-generated overview of the issue and its comments. It does not make any requests to GitHub; the issue and comment data must already be stored in db.
func UpdateOverview ¶
func UpdateOverview(ctx context.Context, lc *llmapp.Client, db storage.DB, project string, issue int64, lastRead int64) (*IssueOverviewResult, error)
UpdateOverview returns an LLM-generated overview of the issue and its comments, separating the comments into "old" and "new" groups broken by the specifed lastRead comment id. (The lastRead comment itself is considered "old", and must be present in the database). UpdateOverview does not make any requests to GitHub; the issue and comment data must already be stored in db.
type Label ¶
type Label struct {
Name string `json:"name"`
}
A Label represents a project issue tracker label in GitHub JSON.
type Milestone ¶
type Milestone struct {
Title string `json:"title"`
}
A Milestone represents a project issue milestone in GitHub JSON.
type Repository ¶
type Repository struct {
Project string `json:"full_name"`
}
Repository is the repository in which an event occurred. https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository
type TestingClient ¶
type TestingClient struct {
// contains filtered or unexported fields
}
A TestingClient provides access to Client functionality intended for testing.
See Client.Testing for a description of testing mode.
func (*TestingClient) AddIssue ¶
func (tc *TestingClient) AddIssue(project string, issue *Issue)
AddIssue adds the given issue to the identified project, assigning it a new issue number starting at 10⁹. AddIssue creates a new entry in the associated Client's underlying database, so other Client's using the same database will see the issue too.
NOTE: Only one TestingClient should be adding issues, since they do not coordinate in the database about ID assignment. Perhaps they should, but normally there is just one Client.
func (*TestingClient) AddIssueComment ¶
func (tc *TestingClient) AddIssueComment(project string, issue int64, comment *IssueComment) int64
AddIssueComment adds the given issue comment to the identified project issue, assigning it a new comment ID starting at 10¹⁰. AddIssueComment creates a new entry in the associated Client's underlying database, so other Client's using the same database will see the issue comment too.
NOTE: Only one TestingClient should be adding issues, since they do not coordinate in the database about ID assignment. Perhaps they should, but normally there is just one Client.
func (*TestingClient) AddIssueEvent ¶
func (tc *TestingClient) AddIssueEvent(project string, issue int64, event *IssueEvent)
AddIssueEvent adds the given issue event to the identified project issue, assigning it a new comment ID starting at 10¹¹. AddIssueEvent creates a new entry in the associated Client's underlying database, so other Client's using the same database will see the issue event too.
NOTE: Only one TestingClient should be adding issues, since they do not coordinate in the database about ID assignment. Perhaps they should, but normally there is just one Client.
func (*TestingClient) ClearEdits ¶
func (tc *TestingClient) ClearEdits()
ClearEdits clears the list of edits that are meant to be applied
func (*TestingClient) Edits ¶
func (tc *TestingClient) Edits() []*TestingEdit
Edits returns a list of all the edits that have been applied using Client methods (for example Client.EditIssue, Client.EditIssueComment, Client.PostIssueComment). These edits have not been applied on GitHub, only diverted into the TestingClient.
See Client.Testing for a description of testing mode.
NOTE: These edits are not applied to the underlying database, since they are also not applied to the underlying database when using a real connection to GitHub; instead we wait for the next sync to download GitHub's view of the edits. See Client.EditIssue.
func (*TestingClient) LoadTxtar ¶
func (tc *TestingClient) LoadTxtar(file string) error
LoadTxtar loads issue histories from the named txtar file, writing them to the database using TestingClient.AddIssue, TestingClient.AddIssueComment, and TestingClient.AddIssueEvent.
The file should contain a txtar archive (see golang.org/x/tools/txtar). Each file in the archive should be named “project#n” (for example “golang/go#123”) and contain an issue history in the format printed by the rsc.io/github/issue command. See the file ../testdata/rsctmp.txt for an example.
To download a specific set of issues into a new file, you can use a script like:
go install rsc.io/github/issue@latest project=golang/go (for i in 1 2 3 4 5 do echo "-- $project#$i --" issue -p $project $i done) > testdata/proj.txt
func (*TestingClient) LoadTxtarData ¶
func (tc *TestingClient) LoadTxtarData(data []byte) error
LoadTxtarData loads issue histories from the txtar file content data. See [LoadTxtar] for a description of the format.
type TestingEdit ¶
type TestingEdit struct { Project string Issue int64 Comment int64 IssueChanges *IssueChanges IssueCommentChanges *IssueCommentChanges }
A TestingEdit is a diverted edit, which was logged instead of actually applied on GitHub.
func (*TestingEdit) String ¶
func (e *TestingEdit) String() string
String returns a basic string representation of the edit.
type User ¶
type User struct {
Login string `json:"login"`
}
A User represents a user or organization account in GitHub JSON.
type WebhookEvent ¶
type WebhookEvent struct { // Type specifies the type of event's Payload. Type WebhookEventType // Payload is the unmarshaled JSON payload of the webhook event, // of the Go type specified by Type. // // Event types that are not implemented use Go type [json.RawMessage]. Payload any }
WebhookEvent contains the data sent in a GitHub webhook request that is relevant for responding to the event.
func ValidateWebhookRequest ¶
ValidateWebhookRequest verifies that the request's payload matches the HMAC tag in the header and returns a WebhookEvent containing the unmarshaled payload.
The function is intended to validate authenticated POST requests received from GitHub webhooks.
It expects:
- a POST request
- an "X-GitHub-Event" header entry with a non-empty event type (see WebhookEventType)
- a non-empty request body containing valid JSON representing an event of the specified event type
- an "X-Hub-Signature-256" header entry of the form "sha256=HMAC", where HMAC is a valid hex-encoded HMAC tag of the request body computed with the key in db named "github-webhook"
The function returns an error if any of these conditions is not met.
func (*WebhookEvent) Project ¶
func (e *WebhookEvent) Project() string
Project returns the GitHub project (e.g., "golang/go") for the event, or an empty string if the project cannot be determined.
type WebhookEventType ¶
type WebhookEventType string
WebhookEventType is the name GitHub uses to refer to a type of GitHub webhook event.
Event types that are not implemented use Go type json.RawMessage.
See https://docs.github.com/en/webhooks/webhook-events-and-payloads for all possible event types.
const ( // An issue event. // Corresponds to Go type [*WebhookIssueEvent]. WebhookEventTypeIssue WebhookEventType = "issues" // An issue comment event. // Corresponds to Go type [*WebhookIssueCommentEvent]. WebhookEventTypeIssueComment WebhookEventType = "issue_comment" )
type WebhookIssueAction ¶
type WebhookIssueAction string
const (
WebhookIssueActionOpened WebhookIssueAction = "opened"
)
type WebhookIssueCommentAction ¶
type WebhookIssueCommentAction string
const (
WebhookIssueCommentActionCreated WebhookIssueCommentAction = "created"
)
type WebhookIssueCommentEvent ¶
type WebhookIssueCommentEvent struct { Action WebhookIssueCommentAction `json:"action"` Issue Issue `json:"issue"` Repository Repository `json:"repository"` }
WebhookIssueEvent is the structure of the JSON payload for a GitHub "issue_comment" event (for example, a new comment posted). https://docs.github.com/en/webhooks/webhook-events-and-payloads#issue_comment
type WebhookIssueEvent ¶
type WebhookIssueEvent struct { Action WebhookIssueAction `json:"action"` Issue Issue `json:"issue"` Repository Repository `json:"repository"` }
WebhookIssueEvent is the structure of the JSON payload for a GitHub "issues" event (for example, a new issue created). https://docs.github.com/en/webhooks/webhook-events-and-payloads#issues