Add godoc comments to exported symbols in pkg/cmd/pr
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
16c6500c82
commit
5de7a10080
36 changed files with 200 additions and 5 deletions
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CheckoutOptions holds the configuration for the pr checkout command.
|
||||
type CheckoutOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
GitClient *git.Client
|
||||
|
|
@ -35,6 +36,7 @@ type CheckoutOptions struct {
|
|||
BranchName string
|
||||
}
|
||||
|
||||
// NewCmdCheckout creates the cobra command for checking out a pull request locally.
|
||||
func NewCmdCheckout(f *cmdutil.Factory, runF func(*CheckoutOptions) error) *cobra.Command {
|
||||
opts := &CheckoutOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
@ -290,6 +292,7 @@ func executeCmds(client *git.Client, credentialPattern git.CredentialPattern, cm
|
|||
return nil
|
||||
}
|
||||
|
||||
// PRResolver resolves a pull request and its base repository for checkout.
|
||||
type PRResolver interface {
|
||||
Resolve() (*api.PullRequest, ghrepo.Interface, error)
|
||||
}
|
||||
|
|
@ -299,6 +302,7 @@ type specificPRResolver struct {
|
|||
selector string
|
||||
}
|
||||
|
||||
// Resolve finds a pull request by its selector string and returns it along with its base repository.
|
||||
func (r *specificPRResolver) Resolve() (*api.PullRequest, ghrepo.Interface, error) {
|
||||
pr, baseRepo, err := r.prFinder.Find(shared.FindOptions{
|
||||
Selector: r.selector,
|
||||
|
|
@ -326,6 +330,7 @@ type promptingPRResolver struct {
|
|||
baseRepo ghrepo.Interface
|
||||
}
|
||||
|
||||
// Resolve interactively prompts the user to select a pull request from a list of open PRs.
|
||||
func (r *promptingPRResolver) Resolve() (*api.PullRequest, ghrepo.Interface, error) {
|
||||
r.io.StartProgressIndicator()
|
||||
listResult, err := r.prLister.List(shared.ListOptions{
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ type checkCounts struct {
|
|||
Canceled int
|
||||
}
|
||||
|
||||
// ExportData returns the check's fields as a map for structured data export.
|
||||
func (ch *check) ExportData(fields []string) map[string]interface{} {
|
||||
return cmdutil.StructExportData(ch, fields)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ var prCheckFields = []string{
|
|||
"description",
|
||||
}
|
||||
|
||||
// ChecksOptions holds the configuration for the pr checks command.
|
||||
type ChecksOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
|
@ -49,6 +50,7 @@ type ChecksOptions struct {
|
|||
Required bool
|
||||
}
|
||||
|
||||
// NewCmdChecks creates the cobra command for viewing CI status checks on a pull request.
|
||||
func NewCmdChecks(f *cmdutil.Factory, runF func(*ChecksOptions) error) *cobra.Command {
|
||||
var interval int
|
||||
opts := &ChecksOptions{
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CloseOptions holds the configuration for the pr close command.
|
||||
type CloseOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
GitClient *git.Client
|
||||
|
|
@ -28,6 +29,7 @@ type CloseOptions struct {
|
|||
DeleteLocalBranch bool
|
||||
}
|
||||
|
||||
// NewCmdClose creates the cobra command for closing a pull request.
|
||||
func NewCmdClose(f *cmdutil.Factory, runF func(*CloseOptions) error) *cobra.Command {
|
||||
opts := &CloseOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewCmdComment creates the cobra command for adding a comment to a pull request.
|
||||
func NewCmdComment(f *cmdutil.Factory, runF func(*shared.CommentableOptions) error) *cobra.Command {
|
||||
opts := &shared.CommentableOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CreateOptions holds the configuration and inputs for creating a pull request.
|
||||
type CreateOptions struct {
|
||||
// This struct stores user input and factory functions
|
||||
Detector fd.Detector
|
||||
|
|
@ -99,10 +100,12 @@ type baseRefs struct {
|
|||
baseBranchName string
|
||||
}
|
||||
|
||||
// BaseRef returns the name of the base branch.
|
||||
func (r baseRefs) BaseRef() string {
|
||||
return r.baseBranchName
|
||||
}
|
||||
|
||||
// BaseRepo returns the base repository for the pull request.
|
||||
func (r baseRefs) BaseRepo() *api.Repository {
|
||||
return r.baseRepo
|
||||
}
|
||||
|
|
@ -114,10 +117,12 @@ type skipPushRefs struct {
|
|||
qualifiedHeadRef shared.QualifiedHeadRef
|
||||
}
|
||||
|
||||
// QualifiedHeadRef returns the fully qualified head ref string for cross-repo or same-repo PRs.
|
||||
func (r skipPushRefs) QualifiedHeadRef() string {
|
||||
return r.qualifiedHeadRef.String()
|
||||
}
|
||||
|
||||
// UnqualifiedHeadRef returns the head branch name without any owner prefix.
|
||||
func (r skipPushRefs) UnqualifiedHeadRef() string {
|
||||
return r.qualifiedHeadRef.BranchName()
|
||||
}
|
||||
|
|
@ -132,6 +137,7 @@ type pushableRefs struct {
|
|||
headBranchName string
|
||||
}
|
||||
|
||||
// QualifiedHeadRef returns the head ref, prefixed with the owner if the head and base repos differ.
|
||||
func (r pushableRefs) QualifiedHeadRef() string {
|
||||
if ghrepo.IsSame(r.headRepo, r.baseRepo) {
|
||||
return r.headBranchName
|
||||
|
|
@ -139,10 +145,12 @@ func (r pushableRefs) QualifiedHeadRef() string {
|
|||
return fmt.Sprintf("%s:%s", r.headRepo.RepoOwner(), r.headBranchName)
|
||||
}
|
||||
|
||||
// UnqualifiedHeadRef returns the head branch name without any owner prefix.
|
||||
func (r pushableRefs) UnqualifiedHeadRef() string {
|
||||
return r.headBranchName
|
||||
}
|
||||
|
||||
// HeadRepo returns the repository where the head branch resides.
|
||||
func (r pushableRefs) HeadRepo() ghrepo.Interface {
|
||||
return r.headRepo
|
||||
}
|
||||
|
|
@ -158,10 +166,12 @@ type forkableRefs struct {
|
|||
qualifiedHeadRef shared.QualifiedHeadRef
|
||||
}
|
||||
|
||||
// QualifiedHeadRef returns the fully qualified head ref string for a fork-based PR.
|
||||
func (r forkableRefs) QualifiedHeadRef() string {
|
||||
return r.qualifiedHeadRef.String()
|
||||
}
|
||||
|
||||
// UnqualifiedHeadRef returns the head branch name without any owner prefix.
|
||||
func (r forkableRefs) UnqualifiedHeadRef() string {
|
||||
return r.qualifiedHeadRef.BranchName()
|
||||
}
|
||||
|
|
@ -190,6 +200,7 @@ type CreateContext struct {
|
|||
GitClient *git.Client
|
||||
}
|
||||
|
||||
// NewCmdCreate creates a new cobra.Command for opening a pull request.
|
||||
func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command {
|
||||
opts := &CreateOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
@ -641,6 +652,7 @@ func initDefaultTitleBody(ctx CreateContext, state *shared.IssueMetadataState, u
|
|||
return nil
|
||||
}
|
||||
|
||||
// NewIssueState creates an IssueMetadataState populated from the given CreateContext and CreateOptions.
|
||||
func NewIssueState(ctx CreateContext, opts CreateOptions) (*shared.IssueMetadataState, error) {
|
||||
var milestoneTitles []string
|
||||
if opts.Milestone != "" {
|
||||
|
|
@ -673,6 +685,7 @@ func NewIssueState(ctx CreateContext, opts CreateOptions) (*shared.IssueMetadata
|
|||
return state, nil
|
||||
}
|
||||
|
||||
// NewCreateContext initializes a CreateContext by resolving remotes, repos, and branch information.
|
||||
func NewCreateContext(opts *CreateOptions) (*CreateContext, error) {
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ import (
|
|||
"regexp"
|
||||
)
|
||||
|
||||
// NewRegexpWriter creates a RegexpWriter that replaces matches of re with repl in the output.
|
||||
func NewRegexpWriter(out io.Writer, re *regexp.Regexp, repl string) *RegexpWriter {
|
||||
return &RegexpWriter{out: out, re: *re, repl: repl}
|
||||
}
|
||||
|
||||
// RegexpWriter is an io.Writer that filters output by replacing lines matching a regexp.
|
||||
type RegexpWriter struct {
|
||||
out io.Writer
|
||||
re regexp.Regexp
|
||||
|
|
@ -17,6 +19,7 @@ type RegexpWriter struct {
|
|||
buf []byte
|
||||
}
|
||||
|
||||
// Write processes data by applying regexp replacements line by line and writing the result.
|
||||
func (s *RegexpWriter) Write(data []byte) (int, error) {
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
|
|
@ -51,6 +54,7 @@ func (s *RegexpWriter) Write(data []byte) (int, error) {
|
|||
return len(data), nil
|
||||
}
|
||||
|
||||
// Flush writes any remaining buffered data after applying regexp replacements.
|
||||
func (s *RegexpWriter) Flush() (int, error) {
|
||||
if len(s.buf) > 0 {
|
||||
repl := []byte(s.repl)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
// DiffOptions holds the configuration for the pr diff command.
|
||||
type DiffOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
|
@ -38,6 +39,7 @@ type DiffOptions struct {
|
|||
BrowserMode bool
|
||||
}
|
||||
|
||||
// NewCmdDiff creates the cobra command for viewing the diff of a pull request.
|
||||
func NewCmdDiff(f *cmdutil.Factory, runF func(*DiffOptions) error) *cobra.Command {
|
||||
opts := &DiffOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// EditOptions holds the configuration for the pr edit command.
|
||||
type EditOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
|
@ -39,6 +40,7 @@ type EditOptions struct {
|
|||
shared.Editable
|
||||
}
|
||||
|
||||
// NewCmdEdit creates the cobra command for editing a pull request's properties.
|
||||
func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Command {
|
||||
opts := &EditOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
@ -529,6 +531,7 @@ func updatePullRequestReviewsREST(client *api.Client, repo ghrepo.Interface, num
|
|||
return wg.Wait()
|
||||
}
|
||||
|
||||
// Surveyor provides interactive prompts for selecting and editing pull request fields.
|
||||
type Surveyor interface {
|
||||
FieldsToEdit(*shared.Editable) error
|
||||
EditFields(*shared.Editable, string) error
|
||||
|
|
@ -538,24 +541,29 @@ type surveyor struct {
|
|||
P shared.EditPrompter
|
||||
}
|
||||
|
||||
// FieldsToEdit prompts the user to select which pull request fields to edit.
|
||||
func (s surveyor) FieldsToEdit(editable *shared.Editable) error {
|
||||
return shared.FieldsToEditSurvey(s.P, editable)
|
||||
}
|
||||
|
||||
// EditFields prompts the user to provide new values for the selected pull request fields.
|
||||
func (s surveyor) EditFields(editable *shared.Editable, editorCmd string) error {
|
||||
return shared.EditFieldsSurvey(s.P, editable, editorCmd)
|
||||
}
|
||||
|
||||
// EditableOptionsFetcher fetches the available options for editable pull request fields.
|
||||
type EditableOptionsFetcher interface {
|
||||
EditableOptionsFetch(*api.Client, ghrepo.Interface, *shared.Editable, gh.ProjectsV1Support) error
|
||||
}
|
||||
|
||||
type fetcher struct{}
|
||||
|
||||
// EditableOptionsFetch retrieves the available options such as labels, assignees, and milestones for editing.
|
||||
func (f fetcher) EditableOptionsFetch(client *api.Client, repo ghrepo.Interface, opts *shared.Editable, projectsV1Support gh.ProjectsV1Support) error {
|
||||
return shared.FetchOptions(client, repo, opts, projectsV1Support)
|
||||
}
|
||||
|
||||
// EditorRetriever retrieves the user's preferred text editor command.
|
||||
type EditorRetriever interface {
|
||||
Retrieve() (string, error)
|
||||
}
|
||||
|
|
@ -564,6 +572,7 @@ type editorRetriever struct {
|
|||
config func() (gh.Config, error)
|
||||
}
|
||||
|
||||
// Retrieve returns the user's configured editor command.
|
||||
func (e editorRetriever) Retrieve() (string, error) {
|
||||
return cmdutil.DetermineEditor(e.config)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ListOptions holds the configuration for the pr list command.
|
||||
type ListOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
|
@ -43,6 +44,7 @@ type ListOptions struct {
|
|||
Now func() time.Time
|
||||
}
|
||||
|
||||
// NewCmdList creates the cobra command for listing pull requests in a repository.
|
||||
func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command {
|
||||
opts := &ListOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
|
|||
|
|
@ -9,21 +9,33 @@ import (
|
|||
"github.com/shurcooL/githubv4"
|
||||
)
|
||||
|
||||
// PullRequestMergeMethod represents the strategy used to merge a pull request.
|
||||
type PullRequestMergeMethod int
|
||||
|
||||
const (
|
||||
// PullRequestMergeMethodMerge creates a merge commit.
|
||||
PullRequestMergeMethodMerge PullRequestMergeMethod = iota
|
||||
// PullRequestMergeMethodRebase rebases the commits onto the base branch.
|
||||
PullRequestMergeMethodRebase
|
||||
// PullRequestMergeMethodSquash squashes all commits into a single commit.
|
||||
PullRequestMergeMethodSquash
|
||||
)
|
||||
|
||||
// Merge state status constants represent the GitHub GraphQL MergeStateStatus values.
|
||||
const (
|
||||
MergeStateStatusBehind = "BEHIND"
|
||||
MergeStateStatusBlocked = "BLOCKED"
|
||||
MergeStateStatusClean = "CLEAN"
|
||||
MergeStateStatusDirty = "DIRTY"
|
||||
// MergeStateStatusBehind indicates the head branch is behind the base branch.
|
||||
MergeStateStatusBehind = "BEHIND"
|
||||
// MergeStateStatusBlocked indicates the merge is blocked by branch protection or other rules.
|
||||
MergeStateStatusBlocked = "BLOCKED"
|
||||
// MergeStateStatusClean indicates the branch is clean and ready to merge.
|
||||
MergeStateStatusClean = "CLEAN"
|
||||
// MergeStateStatusDirty indicates the branch has merge conflicts.
|
||||
MergeStateStatusDirty = "DIRTY"
|
||||
// MergeStateStatusHasHooks indicates the branch has pre-receive hooks that must pass.
|
||||
MergeStateStatusHasHooks = "HAS_HOOKS"
|
||||
MergeStateStatusMerged = "MERGED"
|
||||
// MergeStateStatusMerged indicates the pull request has already been merged.
|
||||
MergeStateStatusMerged = "MERGED"
|
||||
// MergeStateStatusUnstable indicates the branch has failing required status checks.
|
||||
MergeStateStatusUnstable = "UNSTABLE"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ type editor interface {
|
|||
Edit(string, string) (string, error)
|
||||
}
|
||||
|
||||
// MergeOptions holds the configuration for the pr merge command.
|
||||
type MergeOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
GitClient *git.Client
|
||||
|
|
@ -57,6 +58,7 @@ type MergeOptions struct {
|
|||
// ErrAlreadyInMergeQueue indicates that the pull request is already in a merge queue
|
||||
var ErrAlreadyInMergeQueue = errors.New("already in merge queue")
|
||||
|
||||
// NewCmdMerge creates the cobra command for merging a pull request.
|
||||
func NewCmdMerge(f *cmdutil.Factory, runF func(*MergeOptions) error) *cobra.Command {
|
||||
opts := &MergeOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
@ -703,6 +705,7 @@ type userEditor struct {
|
|||
config func() (gh.Config, error)
|
||||
}
|
||||
|
||||
// Edit opens the user's preferred text editor to edit the given text and returns the result.
|
||||
func (e *userEditor) Edit(filename, startingText string) (string, error) {
|
||||
editorCommand, err := cmdutil.DetermineEditor(e.config)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewCmdPR creates the top-level cobra command for managing pull requests.
|
||||
func NewCmdPR(f *cmdutil.Factory) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "pr <command>",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ReadyOptions holds the configuration for the pr ready command.
|
||||
type ReadyOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
|
@ -23,6 +24,7 @@ type ReadyOptions struct {
|
|||
Undo bool
|
||||
}
|
||||
|
||||
// NewCmdReady creates the cobra command for marking a pull request as ready for review.
|
||||
func NewCmdReady(f *cmdutil.Factory, runF func(*ReadyOptions) error) *cobra.Command {
|
||||
opts := &ReadyOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ReopenOptions holds the configuration for the pr reopen command.
|
||||
type ReopenOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
|
@ -22,6 +23,7 @@ type ReopenOptions struct {
|
|||
Comment string
|
||||
}
|
||||
|
||||
// NewCmdReopen creates the cobra command for reopening a closed pull request.
|
||||
func NewCmdReopen(f *cmdutil.Factory, runF func(*ReopenOptions) error) *cobra.Command {
|
||||
opts := &ReopenOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// RevertOptions holds the configuration for the pr revert command.
|
||||
type RevertOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
|
@ -27,6 +28,7 @@ type RevertOptions struct {
|
|||
IsDraft bool
|
||||
}
|
||||
|
||||
// NewCmdRevert creates the cobra command for reverting a merged pull request.
|
||||
func NewCmdRevert(f *cmdutil.Factory, runF func(*RevertOptions) error) *cobra.Command {
|
||||
opts := &RevertOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ReviewOptions holds the configuration for the pr review command.
|
||||
type ReviewOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
Config func() (gh.Config, error)
|
||||
|
|
@ -31,6 +32,7 @@ type ReviewOptions struct {
|
|||
Body string
|
||||
}
|
||||
|
||||
// NewCmdReview creates the cobra command for submitting a review on a pull request.
|
||||
func NewCmdReview(f *cmdutil.Factory, runF func(*ReviewOptions) error) *cobra.Command {
|
||||
opts := &ReviewOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
|
|||
|
|
@ -20,20 +20,26 @@ import (
|
|||
var errNoUserComments = errors.New("no comments found for current user")
|
||||
var errDeleteNotConfirmed = errors.New("deletion not confirmed")
|
||||
|
||||
// InputType represents the method used for providing comment input.
|
||||
type InputType int
|
||||
|
||||
const (
|
||||
// InputTypeEditor indicates input will be provided via an external editor.
|
||||
InputTypeEditor InputType = iota
|
||||
// InputTypeInline indicates input is provided directly as a flag value.
|
||||
InputTypeInline
|
||||
// InputTypeWeb indicates the comment will be created via the browser.
|
||||
InputTypeWeb
|
||||
)
|
||||
|
||||
// Commentable represents an entity that can be commented on, such as an issue or pull request.
|
||||
type Commentable interface {
|
||||
Link() string
|
||||
Identifier() string
|
||||
CurrentUserComments() []api.Comment
|
||||
}
|
||||
|
||||
// CommentableOptions holds the configuration for creating, updating, or deleting comments.
|
||||
type CommentableOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
HttpClient func() (*http.Client, error)
|
||||
|
|
@ -55,6 +61,7 @@ type CommentableOptions struct {
|
|||
Host string
|
||||
}
|
||||
|
||||
// CommentablePreRun validates flags and configures input type and interactivity before the command runs.
|
||||
func CommentablePreRun(cmd *cobra.Command, opts *CommentableOptions) error {
|
||||
inputFlags := 0
|
||||
if cmd.Flags().Changed("body") {
|
||||
|
|
@ -105,6 +112,7 @@ func CommentablePreRun(cmd *cobra.Command, opts *CommentableOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// CommentableRun executes the comment action (create, update, or delete) based on the configured options.
|
||||
func CommentableRun(opts *CommentableOptions) error {
|
||||
commentable, repo, err := opts.RetrieveCommentable()
|
||||
if err != nil {
|
||||
|
|
@ -305,12 +313,14 @@ func deleteComment(commentable Commentable, opts *CommentableOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// CommentableConfirmSubmitSurvey returns a function that prompts the user to confirm comment submission.
|
||||
func CommentableConfirmSubmitSurvey(p Prompt) func() (bool, error) {
|
||||
return func() (bool, error) {
|
||||
return p.Confirm("Submit?", true)
|
||||
}
|
||||
}
|
||||
|
||||
// CommentableInteractiveEditSurvey returns a function that opens an editor for composing a comment interactively.
|
||||
func CommentableInteractiveEditSurvey(cf func() (gh.Config, error), io *iostreams.IOStreams) func(string) (string, error) {
|
||||
return func(initialValue string) (string, error) {
|
||||
editorCommand, err := cmdutil.DetermineEditor(cf)
|
||||
|
|
@ -324,12 +334,14 @@ func CommentableInteractiveEditSurvey(cf func() (gh.Config, error), io *iostream
|
|||
}
|
||||
}
|
||||
|
||||
// CommentableInteractiveCreateIfNoneSurvey returns a function that prompts the user to create a comment when none exist.
|
||||
func CommentableInteractiveCreateIfNoneSurvey(p Prompt) func() (bool, error) {
|
||||
return func() (bool, error) {
|
||||
return p.Confirm("No comments found. Create one?", true)
|
||||
}
|
||||
}
|
||||
|
||||
// CommentableEditSurvey returns a function that opens an editor for composing a comment non-interactively.
|
||||
func CommentableEditSurvey(cf func() (gh.Config, error), io *iostreams.IOStreams) func(string) (string, error) {
|
||||
return func(initialValue string) (string, error) {
|
||||
editorCommand, err := cmdutil.DetermineEditor(cf)
|
||||
|
|
@ -340,6 +352,7 @@ func CommentableEditSurvey(cf func() (gh.Config, error), io *iostreams.IOStreams
|
|||
}
|
||||
}
|
||||
|
||||
// CommentableConfirmDeleteLastComment returns a function that prompts the user to confirm deletion of their last comment.
|
||||
func CommentableConfirmDeleteLastComment(p Prompt) func(string) (bool, error) {
|
||||
return func(body string) (bool, error) {
|
||||
return p.Confirm(fmt.Sprintf("Delete the comment: %q?", body), true)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/markdown"
|
||||
)
|
||||
|
||||
// Comment represents a comment on an issue or pull request with metadata accessors.
|
||||
type Comment interface {
|
||||
Identifier() string
|
||||
AuthorLogin() string
|
||||
|
|
@ -26,6 +27,7 @@ type Comment interface {
|
|||
Status() string
|
||||
}
|
||||
|
||||
// RawCommentList returns a plaintext representation of comments and reviews sorted by creation time.
|
||||
func RawCommentList(comments api.Comments, reviews api.PullRequestReviews) string {
|
||||
sortedComments := sortComments(comments, reviews)
|
||||
var b strings.Builder
|
||||
|
|
@ -50,6 +52,7 @@ func formatRawComment(comment Comment) string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
// CommentList returns a formatted string of comments and reviews, optionally showing only the newest.
|
||||
func CommentList(io *iostreams.IOStreams, comments api.Comments, reviews api.PullRequestReviews, preview bool) (string, error) {
|
||||
sortedComments := sortComments(comments, reviews)
|
||||
if preview && len(sortedComments) > 0 {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
)
|
||||
|
||||
// RequestableReviewersForCompletion returns a sorted list of requestable reviewers for shell completion.
|
||||
func RequestableReviewersForCompletion(httpClient *http.Client, repo ghrepo.Interface) ([]string, error) {
|
||||
client := api.NewClientFromHTTP(api.NewCachedHTTPClient(httpClient, time.Minute*2))
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
)
|
||||
|
||||
// StateTitleWithColor returns the title-cased PR state colorized appropriately, showing "Draft" for draft PRs.
|
||||
func StateTitleWithColor(cs *iostreams.ColorScheme, pr api.PullRequest) string {
|
||||
prStateColorFunc := cs.ColorFromString(ColorForPRState(pr))
|
||||
if pr.State == "OPEN" && pr.IsDraft {
|
||||
|
|
@ -17,6 +18,7 @@ func StateTitleWithColor(cs *iostreams.ColorScheme, pr api.PullRequest) string {
|
|||
return prStateColorFunc(text.Title(pr.State))
|
||||
}
|
||||
|
||||
// PrStateWithDraft returns the PR state string, substituting "DRAFT" for open draft PRs.
|
||||
func PrStateWithDraft(pr *api.PullRequest) string {
|
||||
if pr.IsDraft && pr.State == "OPEN" {
|
||||
return "DRAFT"
|
||||
|
|
@ -25,6 +27,7 @@ func PrStateWithDraft(pr *api.PullRequest) string {
|
|||
return pr.State
|
||||
}
|
||||
|
||||
// ColorForPRState returns a color name suitable for the given pull request's state and draft status.
|
||||
func ColorForPRState(pr api.PullRequest) string {
|
||||
switch pr.State {
|
||||
case "OPEN":
|
||||
|
|
@ -41,6 +44,7 @@ func ColorForPRState(pr api.PullRequest) string {
|
|||
}
|
||||
}
|
||||
|
||||
// ColorForIssueState returns a color name suitable for the given issue's state and state reason.
|
||||
func ColorForIssueState(issue api.Issue) string {
|
||||
switch issue.State {
|
||||
case "OPEN":
|
||||
|
|
@ -55,14 +59,17 @@ func ColorForIssueState(issue api.Issue) string {
|
|||
}
|
||||
}
|
||||
|
||||
// PrintHeader prints a bold header line to the IOStreams output.
|
||||
func PrintHeader(io *iostreams.IOStreams, s string) {
|
||||
fmt.Fprintln(io.Out, io.ColorScheme().Bold(s))
|
||||
}
|
||||
|
||||
// PrintMessage prints a muted message line to the IOStreams output.
|
||||
func PrintMessage(io *iostreams.IOStreams, s string) {
|
||||
fmt.Fprintln(io.Out, io.ColorScheme().Muted(s))
|
||||
}
|
||||
|
||||
// ListNoResults returns a NoResultsError with a message appropriate for whether filters were applied.
|
||||
func ListNoResults(repoName string, itemName string, hasFilters bool) error {
|
||||
if hasFilters {
|
||||
return cmdutil.NewNoResultsError(fmt.Sprintf("no %ss match your search in %s", itemName, repoName))
|
||||
|
|
@ -70,6 +77,7 @@ func ListNoResults(repoName string, itemName string, hasFilters bool) error {
|
|||
return cmdutil.NewNoResultsError(fmt.Sprintf("no open %ss in %s", itemName, repoName))
|
||||
}
|
||||
|
||||
// ListHeader returns a summary string describing how many items are shown out of the total, with filter context.
|
||||
func ListHeader(repoName string, itemName string, matchCount int, totalMatchCount int, hasFilters bool) string {
|
||||
if hasFilters {
|
||||
matchVerb := "match"
|
||||
|
|
@ -82,6 +90,7 @@ func ListHeader(repoName string, itemName string, matchCount int, totalMatchCoun
|
|||
return fmt.Sprintf("Showing %d of %s in %s", matchCount, text.Pluralize(totalMatchCount, fmt.Sprintf("open %s", itemName)), repoName)
|
||||
}
|
||||
|
||||
// PrCheckStatusSummaryWithColor returns a colorized one-line summary of a pull request's CI check status.
|
||||
func PrCheckStatusSummaryWithColor(cs *iostreams.ColorScheme, checks api.PullRequestChecksStatus) string {
|
||||
var summary = cs.Muted("No checks")
|
||||
if checks.Total > 0 {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/set"
|
||||
)
|
||||
|
||||
// Editable collects the editable fields of an issue or pull request for interactive or flag-driven editing.
|
||||
type Editable struct {
|
||||
Title EditableString
|
||||
Body EditableString
|
||||
|
|
@ -24,6 +25,7 @@ type Editable struct {
|
|||
Metadata api.RepoMetadataResult
|
||||
}
|
||||
|
||||
// EditableString holds a single string field that can be edited, along with its default value and valid options.
|
||||
type EditableString struct {
|
||||
Value string
|
||||
Default string
|
||||
|
|
@ -31,6 +33,7 @@ type EditableString struct {
|
|||
Edited bool
|
||||
}
|
||||
|
||||
// EditableSlice holds a list field that supports adding and removing values, along with defaults and valid options.
|
||||
type EditableSlice struct {
|
||||
Value []string
|
||||
Add []string
|
||||
|
|
@ -62,6 +65,7 @@ type EditableProjects struct {
|
|||
ProjectItems map[string]string
|
||||
}
|
||||
|
||||
// Dirty reports whether any field in the Editable has been modified.
|
||||
func (e Editable) Dirty() bool {
|
||||
return e.Title.Edited ||
|
||||
e.Body.Edited ||
|
||||
|
|
@ -73,6 +77,7 @@ func (e Editable) Dirty() bool {
|
|||
e.Milestone.Edited
|
||||
}
|
||||
|
||||
// TitleValue returns a pointer to the edited title, or nil if the title was not edited.
|
||||
func (e Editable) TitleValue() *string {
|
||||
if !e.Title.Edited {
|
||||
return nil
|
||||
|
|
@ -80,6 +85,7 @@ func (e Editable) TitleValue() *string {
|
|||
return &e.Title.Value
|
||||
}
|
||||
|
||||
// BodyValue returns a pointer to the edited body, or nil if the body was not edited.
|
||||
func (e Editable) BodyValue() *string {
|
||||
if !e.Body.Edited {
|
||||
return nil
|
||||
|
|
@ -87,6 +93,7 @@ func (e Editable) BodyValue() *string {
|
|||
return &e.Body.Value
|
||||
}
|
||||
|
||||
// AssigneeIds resolves the edited assignees to their node IDs, applying any add/remove modifications.
|
||||
func (e Editable) AssigneeIds(client *api.Client, repo ghrepo.Interface) (*[]string, error) {
|
||||
if !e.Assignees.Edited {
|
||||
return nil, nil
|
||||
|
|
@ -208,6 +215,7 @@ func (e Editable) ProjectV2Ids() (*[]string, *[]string, error) {
|
|||
return &addIds, &removeIds, nil
|
||||
}
|
||||
|
||||
// MilestoneId resolves the edited milestone title to its node ID, or returns an empty string to clear it.
|
||||
func (e Editable) MilestoneId() (*string, error) {
|
||||
if !e.Milestone.Edited {
|
||||
return nil, nil
|
||||
|
|
@ -288,6 +296,7 @@ func (ep *EditableProjects) clone() EditableProjects {
|
|||
}
|
||||
}
|
||||
|
||||
// EditPrompter defines the prompting interface used by interactive editing surveys.
|
||||
type EditPrompter interface {
|
||||
Select(string, string, []string) (int, error)
|
||||
Input(string, string) (string, error)
|
||||
|
|
@ -297,6 +306,7 @@ type EditPrompter interface {
|
|||
Confirm(string, bool) (bool, error)
|
||||
}
|
||||
|
||||
// EditFieldsSurvey interactively prompts the user to provide values for each edited field and confirms submission.
|
||||
func EditFieldsSurvey(p EditPrompter, editable *Editable, editorCommand string) error {
|
||||
var err error
|
||||
if editable.Title.Edited {
|
||||
|
|
@ -396,6 +406,7 @@ func EditFieldsSurvey(p EditPrompter, editable *Editable, editorCommand string)
|
|||
return nil
|
||||
}
|
||||
|
||||
// FieldsToEditSurvey prompts the user to select which fields they want to edit and marks them on the Editable.
|
||||
func FieldsToEditSurvey(p EditPrompter, editable *Editable) error {
|
||||
contains := func(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
|
|
@ -441,6 +452,7 @@ func FieldsToEditSurvey(p EditPrompter, editable *Editable) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// FetchOptions retrieves repository metadata (reviewers, assignees, labels, projects, milestones) needed for editing.
|
||||
func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable, projectV1Support gh.ProjectsV1Support) error {
|
||||
// Determine whether to fetch organization teams and reviewers.
|
||||
// Interactive reviewer editing (Edited true, but no Add/Remove slices) still needs
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// UpdateIssue applies the edited fields to an issue or pull request via the GitHub API, handling labels, projects, and other fields concurrently.
|
||||
func UpdateIssue(httpClient *http.Client, repo ghrepo.Interface, id string, isPR bool, options Editable) error {
|
||||
var wg errgroup.Group
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ func NewQualifiedHeadRef(owner string, branchName string) QualifiedHeadRef {
|
|||
}
|
||||
}
|
||||
|
||||
// NewQualifiedHeadRefWithoutOwner creates a QualifiedHeadRef with only a branch name and no owner.
|
||||
func NewQualifiedHeadRefWithoutOwner(branchName string) QualifiedHeadRef {
|
||||
return QualifiedHeadRef{
|
||||
owner: o.None[string](),
|
||||
|
|
@ -74,6 +75,7 @@ func (r QualifiedHeadRef) String() string {
|
|||
return r.branchName
|
||||
}
|
||||
|
||||
// BranchName returns the branch name component of the qualified head ref.
|
||||
func (r QualifiedHeadRef) BranchName() string {
|
||||
return r.branchName
|
||||
}
|
||||
|
|
@ -97,6 +99,7 @@ func (r PRFindRefs) QualifiedHeadRef() string {
|
|||
return r.qualifiedHeadRef.String()
|
||||
}
|
||||
|
||||
// UnqualifiedHeadRef returns the branch name without any owner prefix.
|
||||
func (r PRFindRefs) UnqualifiedHeadRef() string {
|
||||
return r.qualifiedHeadRef.BranchName()
|
||||
}
|
||||
|
|
@ -109,10 +112,12 @@ func (r PRFindRefs) Matches(baseBranchName, qualifiedHeadRef string) bool {
|
|||
return headMatches && baseMatches
|
||||
}
|
||||
|
||||
// BaseRepo returns the base repository used for finding the pull request.
|
||||
func (r PRFindRefs) BaseRepo() ghrepo.Interface {
|
||||
return r.baseRepo
|
||||
}
|
||||
|
||||
// RemoteNameToRepoFn is a function type that resolves a git remote name to a repository interface.
|
||||
type RemoteNameToRepoFn func(remoteName string) (ghrepo.Interface, error)
|
||||
|
||||
// PullRequestFindRefsResolver interrogates git configuration to try and determine
|
||||
|
|
@ -122,6 +127,7 @@ type PullRequestFindRefsResolver struct {
|
|||
RemoteNameToRepoFn RemoteNameToRepoFn
|
||||
}
|
||||
|
||||
// NewPullRequestFindRefsResolver creates a PullRequestFindRefsResolver with the given git config client and remotes function.
|
||||
func NewPullRequestFindRefsResolver(gitConfigClient GitConfigClient, remotesFn func() (ghContext.Remotes, error)) PullRequestFindRefsResolver {
|
||||
return PullRequestFindRefsResolver{
|
||||
GitConfigClient: gitConfigClient,
|
||||
|
|
@ -245,6 +251,7 @@ type remoteToRepoResolver struct {
|
|||
remoteNameToRepo RemoteNameToRepoFn
|
||||
}
|
||||
|
||||
// NewRemoteToRepoResolver creates a remoteToRepoResolver that maps git remotes to repository interfaces.
|
||||
func NewRemoteToRepoResolver(remotesFn func() (ghContext.Remotes, error)) remoteToRepoResolver {
|
||||
return remoteToRepoResolver{
|
||||
remoteNameToRepo: newRemoteNameToRepoFn(remotesFn),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// PRFinder finds a pull request by selector, returning the PR and its base repository.
|
||||
type PRFinder interface {
|
||||
Find(opts FindOptions) (*api.PullRequest, ghrepo.Interface, error)
|
||||
}
|
||||
|
|
@ -35,6 +36,7 @@ type progressIndicator interface {
|
|||
StopProgressIndicator()
|
||||
}
|
||||
|
||||
// GitConfigClient provides access to git branch configuration and push settings.
|
||||
type GitConfigClient interface {
|
||||
ReadBranchConfig(ctx context.Context, branchName string) (git.BranchConfig, error)
|
||||
PushDefault(ctx context.Context) (git.PushDefault, error)
|
||||
|
|
@ -55,6 +57,7 @@ type finder struct {
|
|||
branchName string
|
||||
}
|
||||
|
||||
// NewFinder creates a PRFinder backed by the given factory, or returns a test stub if one has been set.
|
||||
func NewFinder(factory *cmdutil.Factory) PRFinder {
|
||||
if finderForRunCommandStyleTests != nil {
|
||||
f := finderForRunCommandStyleTests
|
||||
|
|
@ -92,6 +95,7 @@ func StubFinderForRunCommandStyleTests(t *testing.T, selector string, pr *api.Pu
|
|||
return finder
|
||||
}
|
||||
|
||||
// FindOptions specifies the parameters for finding a pull request.
|
||||
type FindOptions struct {
|
||||
// Selector can be a number with optional `#` prefix, a branch name with optional `<owner>:` prefix, or
|
||||
// a PR URL.
|
||||
|
|
@ -108,6 +112,7 @@ type FindOptions struct {
|
|||
Detector fd.Detector
|
||||
}
|
||||
|
||||
// Find locates a pull request by number, URL, or branch name, fetching the requested GraphQL fields.
|
||||
func (f *finder) Find(opts FindOptions) (*api.PullRequest, ghrepo.Interface, error) {
|
||||
// If we have a URL, we don't need git stuff
|
||||
if len(opts.Fields) == 0 {
|
||||
|
|
@ -619,14 +624,17 @@ func preloadPrChecks(client *http.Client, repo ghrepo.Interface, pr *api.PullReq
|
|||
return nil
|
||||
}
|
||||
|
||||
// NotFoundError indicates that no pull request matching the query was found.
|
||||
type NotFoundError struct {
|
||||
error
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying error wrapped by NotFoundError.
|
||||
func (err *NotFoundError) Unwrap() error {
|
||||
return err.error
|
||||
}
|
||||
|
||||
// NewMockFinder creates a mockFinder for use in tests, returning the given PR and repo when Find is called.
|
||||
func NewMockFinder(selector string, pr *api.PullRequest, repo ghrepo.Interface) *mockFinder {
|
||||
var err error
|
||||
if pr == nil {
|
||||
|
|
@ -649,6 +657,7 @@ type mockFinder struct {
|
|||
err error
|
||||
}
|
||||
|
||||
// Find returns the mocked pull request and repository, validating the selector and fields expectations.
|
||||
func (m *mockFinder) Find(opts FindOptions) (*api.PullRequest, ghrepo.Interface, error) {
|
||||
if m.err != nil {
|
||||
return nil, nil, m.err
|
||||
|
|
@ -672,6 +681,7 @@ func (m *mockFinder) Find(opts FindOptions) (*api.PullRequest, ghrepo.Interface,
|
|||
return m.pr, m.repo, nil
|
||||
}
|
||||
|
||||
// ExpectFields sets the expected GraphQL fields that Find must be called with.
|
||||
func (m *mockFinder) ExpectFields(fields []string) {
|
||||
m.expectFields = fields
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ import (
|
|||
|
||||
var _ GitConfigClient = &CachedBranchConfigGitConfigClient{}
|
||||
|
||||
// CachedBranchConfigGitConfigClient wraps a GitConfigClient to return a pre-fetched BranchConfig instead of querying git.
|
||||
type CachedBranchConfigGitConfigClient struct {
|
||||
CachedBranchConfig git.BranchConfig
|
||||
GitConfigClient
|
||||
}
|
||||
|
||||
// ReadBranchConfig returns the cached BranchConfig, ignoring the branch name argument.
|
||||
func (c CachedBranchConfigGitConfigClient) ReadBranchConfig(ctx context.Context, branchName string) (git.BranchConfig, error) {
|
||||
return c.CachedBranchConfig, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ import (
|
|||
api "github.com/cli/cli/v2/api"
|
||||
)
|
||||
|
||||
// PRLister is the interface for listing pull requests from a repository.
|
||||
type PRLister interface {
|
||||
List(opt ListOptions) (*api.PullRequestAndTotalCount, error)
|
||||
}
|
||||
|
||||
// ListOptions specifies filtering and pagination options for listing pull requests.
|
||||
type ListOptions struct {
|
||||
BaseRepo ghrepo.Interface
|
||||
|
||||
|
|
@ -29,12 +31,14 @@ type lister struct {
|
|||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewLister creates a PRLister that fetches pull requests via the GitHub GraphQL API.
|
||||
func NewLister(httpClient *http.Client) PRLister {
|
||||
return &lister{
|
||||
httpClient: httpClient,
|
||||
}
|
||||
}
|
||||
|
||||
// List retrieves pull requests from the repository matching the given options.
|
||||
func (l *lister) List(opts ListOptions) (*api.PullRequestAndTotalCount, error) {
|
||||
type response struct {
|
||||
Repository struct {
|
||||
|
|
@ -153,6 +157,7 @@ type mockLister struct {
|
|||
err error
|
||||
}
|
||||
|
||||
// NewMockLister creates a mock PRLister that returns the given result and error for testing.
|
||||
func NewMockLister(result *api.PullRequestAndTotalCount, err error) *mockLister {
|
||||
return &mockLister{
|
||||
result: result,
|
||||
|
|
@ -160,6 +165,7 @@ func NewMockLister(result *api.PullRequestAndTotalCount, err error) *mockLister
|
|||
}
|
||||
}
|
||||
|
||||
// List returns the preconfigured result and error, validating expected fields if set.
|
||||
func (m *mockLister) List(opt ListOptions) (*api.PullRequestAndTotalCount, error) {
|
||||
m.called = true
|
||||
if m.err != nil {
|
||||
|
|
@ -176,6 +182,7 @@ func (m *mockLister) List(opt ListOptions) (*api.PullRequestAndTotalCount, error
|
|||
return m.result, m.err
|
||||
}
|
||||
|
||||
// ExpectFields sets the GraphQL fields that the mock expects to receive in List calls.
|
||||
func (m *mockLister) ExpectFields(fields []string) {
|
||||
m.expectFields = fields
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/google/shlex"
|
||||
)
|
||||
|
||||
// WithPrAndIssueQueryParams appends issue or pull request metadata from state as query parameters to the given URL.
|
||||
func WithPrAndIssueQueryParams(client *api.Client, baseRepo ghrepo.Interface, baseURL string, state IssueMetadataState, projectsV1Support gh.ProjectsV1Support) (string, error) {
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
|
|
@ -56,6 +57,7 @@ func ValidURL(urlStr string) bool {
|
|||
return len(urlStr) < 8192
|
||||
}
|
||||
|
||||
// AddMetadataToIssueParams resolves metadata such as assignees, labels, projects, milestones, and reviewers into IDs and adds them to the params map.
|
||||
func AddMetadataToIssueParams(client *api.Client, baseRepo ghrepo.Interface, params map[string]interface{}, tb *IssueMetadataState, projectV1Support gh.ProjectsV1Support) error {
|
||||
if !tb.HasMetadata() {
|
||||
return nil
|
||||
|
|
@ -139,6 +141,7 @@ func AddMetadataToIssueParams(client *api.Client, baseRepo ghrepo.Interface, par
|
|||
return nil
|
||||
}
|
||||
|
||||
// FilterOptions holds the filtering criteria used when listing issues or pull requests.
|
||||
type FilterOptions struct {
|
||||
Assignee string
|
||||
Author string
|
||||
|
|
@ -155,6 +158,7 @@ type FilterOptions struct {
|
|||
State string
|
||||
}
|
||||
|
||||
// IsDefault reports whether the filter options represent the default state with no custom filters applied.
|
||||
func (opts *FilterOptions) IsDefault() bool {
|
||||
if opts.State != "open" {
|
||||
return false
|
||||
|
|
@ -186,6 +190,7 @@ func (opts *FilterOptions) IsDefault() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// ListURLWithQuery builds a URL by appending a search query derived from FilterOptions to the given list URL.
|
||||
func ListURLWithQuery(listURL string, options FilterOptions, advancedIssueSearchSyntax bool) (string, error) {
|
||||
u, err := url.Parse(listURL)
|
||||
if err != nil {
|
||||
|
|
@ -199,6 +204,7 @@ func ListURLWithQuery(listURL string, options FilterOptions, advancedIssueSearch
|
|||
return u.String(), nil
|
||||
}
|
||||
|
||||
// SearchQueryBuild constructs a GitHub search query string from the given FilterOptions.
|
||||
func SearchQueryBuild(options FilterOptions, advancedIssueSearchSyntax bool) string {
|
||||
var is, state string
|
||||
switch options.State {
|
||||
|
|
@ -231,6 +237,7 @@ func SearchQueryBuild(options FilterOptions, advancedIssueSearchSyntax bool) str
|
|||
return query.AdvancedIssueSearchString()
|
||||
}
|
||||
|
||||
// QueryHasStateClause reports whether the search query contains an explicit state or merged filter clause.
|
||||
func QueryHasStateClause(searchQuery string) bool {
|
||||
argv, err := shlex.Split(searchQuery)
|
||||
if err != nil {
|
||||
|
|
@ -253,6 +260,7 @@ type MeReplacer struct {
|
|||
login string
|
||||
}
|
||||
|
||||
// NewMeReplacer creates a MeReplacer that resolves @me to the currently authenticated user's login.
|
||||
func NewMeReplacer(apiClient *api.Client, hostname string) *MeReplacer {
|
||||
return &MeReplacer{
|
||||
apiClient: apiClient,
|
||||
|
|
@ -272,6 +280,7 @@ func (r *MeReplacer) currentLogin() (string, error) {
|
|||
return login, nil
|
||||
}
|
||||
|
||||
// Replace substitutes "@me" with the current user's login, returning other handles unchanged.
|
||||
func (r *MeReplacer) Replace(handle string) (string, error) {
|
||||
if handle == "@me" {
|
||||
return r.currentLogin()
|
||||
|
|
@ -279,6 +288,7 @@ func (r *MeReplacer) Replace(handle string) (string, error) {
|
|||
return handle, nil
|
||||
}
|
||||
|
||||
// ReplaceSlice applies Replace to each element in the slice, substituting any "@me" occurrences.
|
||||
func (r *MeReplacer) ReplaceSlice(handles []string) ([]string, error) {
|
||||
res := make([]string, len(handles))
|
||||
for i, h := range handles {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
)
|
||||
|
||||
// PreserveInput returns a cleanup function that saves issue or PR metadata state to a temporary file when a creation error occurs, enabling recovery with --recover.
|
||||
func PreserveInput(io *iostreams.IOStreams, state *IssueMetadataState, createErr *error) func() {
|
||||
return func() {
|
||||
if !state.IsDirty() {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/cli/cli/v2/api"
|
||||
)
|
||||
|
||||
// ReactionGroupList formats a list of reaction groups into a human-readable string with emoji and counts.
|
||||
func ReactionGroupList(rgs api.ReactionGroups) string {
|
||||
var rs []string
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,13 @@ import (
|
|||
type metadataStateType int
|
||||
|
||||
const (
|
||||
// IssueMetadata indicates that the metadata state is for an issue.
|
||||
IssueMetadata metadataStateType = iota
|
||||
// PRMetadata indicates that the metadata state is for a pull request.
|
||||
PRMetadata
|
||||
)
|
||||
|
||||
// IssueMetadataState holds the editable metadata for an issue or pull request during creation or editing.
|
||||
type IssueMetadataState struct {
|
||||
Type metadataStateType
|
||||
|
||||
|
|
@ -38,14 +41,17 @@ type IssueMetadataState struct {
|
|||
dirty bool // whether user i/o has modified this
|
||||
}
|
||||
|
||||
// MarkDirty flags the state as having been modified by user input.
|
||||
func (tb *IssueMetadataState) MarkDirty() {
|
||||
tb.dirty = true
|
||||
}
|
||||
|
||||
// IsDirty reports whether the state has been modified by user input or has metadata set.
|
||||
func (tb *IssueMetadataState) IsDirty() bool {
|
||||
return tb.dirty || tb.HasMetadata()
|
||||
}
|
||||
|
||||
// HasMetadata reports whether any reviewers, assignees, labels, projects, or milestones are set.
|
||||
func (tb *IssueMetadataState) HasMetadata() bool {
|
||||
return len(tb.Reviewers) > 0 ||
|
||||
len(tb.Assignees) > 0 ||
|
||||
|
|
@ -54,6 +60,7 @@ func (tb *IssueMetadataState) HasMetadata() bool {
|
|||
len(tb.Milestones) > 0
|
||||
}
|
||||
|
||||
// FillFromJSON populates the given IssueMetadataState by reading and unmarshaling a JSON recovery file.
|
||||
func FillFromJSON(io *iostreams.IOStreams, recoverFile string, state *IssueMetadataState) error {
|
||||
var data []byte
|
||||
var err error
|
||||
|
|
|
|||
|
|
@ -15,15 +15,23 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/surveyext"
|
||||
)
|
||||
|
||||
// Action represents a user-selected action in a submission prompt.
|
||||
type Action int
|
||||
|
||||
const (
|
||||
// SubmitAction indicates the user chose to submit.
|
||||
SubmitAction Action = iota
|
||||
// PreviewAction indicates the user chose to continue in the browser.
|
||||
PreviewAction
|
||||
// CancelAction indicates the user cancelled the operation.
|
||||
CancelAction
|
||||
// MetadataAction indicates the user chose to add metadata.
|
||||
MetadataAction
|
||||
// EditCommitMessageAction indicates the user chose to edit the commit message.
|
||||
EditCommitMessageAction
|
||||
// EditCommitSubjectAction indicates the user chose to edit the commit subject.
|
||||
EditCommitSubjectAction
|
||||
// SubmitDraftAction indicates the user chose to submit as a draft.
|
||||
SubmitDraftAction
|
||||
|
||||
noMilestone = "(none)"
|
||||
|
|
@ -35,6 +43,7 @@ const (
|
|||
cancelLabel = "Cancel"
|
||||
)
|
||||
|
||||
// Prompt defines the interface for interactive user prompts used during issue and PR creation.
|
||||
type Prompt interface {
|
||||
Input(prompt string, defaultValue string) (string, error)
|
||||
Select(prompt string, defaultValue string, options []string) (int, error)
|
||||
|
|
@ -44,10 +53,12 @@ type Prompt interface {
|
|||
MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) prompter.MultiSelectSearchResult) ([]string, error)
|
||||
}
|
||||
|
||||
// ConfirmIssueSubmission prompts the user to select a submission action for an issue.
|
||||
func ConfirmIssueSubmission(p Prompt, allowPreview bool, allowMetadata bool) (Action, error) {
|
||||
return confirmSubmission(p, allowPreview, allowMetadata, false, false)
|
||||
}
|
||||
|
||||
// ConfirmPRSubmission prompts the user to select a submission action for a pull request.
|
||||
func ConfirmPRSubmission(p Prompt, allowPreview, allowMetadata, isDraft bool) (Action, error) {
|
||||
return confirmSubmission(p, allowPreview, allowMetadata, true, isDraft)
|
||||
}
|
||||
|
|
@ -89,6 +100,7 @@ func confirmSubmission(p Prompt, allowPreview, allowMetadata, allowDraft, isDraf
|
|||
}
|
||||
}
|
||||
|
||||
// BodySurvey prompts the user to edit the body of an issue or pull request using a markdown editor.
|
||||
func BodySurvey(p Prompt, state *IssueMetadataState, templateContent string) error {
|
||||
if templateContent != "" {
|
||||
if state.Body != "" {
|
||||
|
|
@ -113,6 +125,7 @@ func BodySurvey(p Prompt, state *IssueMetadataState, templateContent string) err
|
|||
return nil
|
||||
}
|
||||
|
||||
// TitleSurvey prompts the user to enter a required title for an issue or pull request.
|
||||
func TitleSurvey(p Prompt, io *iostreams.IOStreams, state *IssueMetadataState) error {
|
||||
var err error
|
||||
result := ""
|
||||
|
|
@ -135,6 +148,7 @@ func TitleSurvey(p Prompt, io *iostreams.IOStreams, state *IssueMetadataState) e
|
|||
return nil
|
||||
}
|
||||
|
||||
// MetadataFetcher fetches repository metadata such as assignees, labels, and projects for use in interactive prompts.
|
||||
type MetadataFetcher struct {
|
||||
IO *iostreams.IOStreams
|
||||
APIClient *api.Client
|
||||
|
|
@ -142,6 +156,7 @@ type MetadataFetcher struct {
|
|||
State *IssueMetadataState
|
||||
}
|
||||
|
||||
// RepoMetadataFetch retrieves repository metadata based on the given input and caches the result in State.
|
||||
func (mf *MetadataFetcher) RepoMetadataFetch(input api.RepoMetadataInput) (*api.RepoMetadataResult, error) {
|
||||
mf.IO.StartProgressIndicator()
|
||||
metadataResult, err := api.RepoMetadata(mf.APIClient, mf.Repo, input)
|
||||
|
|
@ -150,10 +165,12 @@ func (mf *MetadataFetcher) RepoMetadataFetch(input api.RepoMetadataInput) (*api.
|
|||
return metadataResult, err
|
||||
}
|
||||
|
||||
// RepoMetadataFetcher is the interface for fetching repository metadata used during metadata surveys.
|
||||
type RepoMetadataFetcher interface {
|
||||
RepoMetadataFetch(api.RepoMetadataInput) (*api.RepoMetadataResult, error)
|
||||
}
|
||||
|
||||
// MetadataSurvey interactively prompts the user to select and set metadata such as reviewers, assignees, labels, projects, and milestones.
|
||||
func MetadataSurvey(p Prompt, io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher RepoMetadataFetcher, state *IssueMetadataState, projectsV1Support gh.ProjectsV1Support) error {
|
||||
isChosen := func(m string) bool {
|
||||
for _, c := range state.Metadata {
|
||||
|
|
@ -360,15 +377,18 @@ func MetadataSurvey(p Prompt, io *iostreams.IOStreams, baseRepo ghrepo.Interface
|
|||
return nil
|
||||
}
|
||||
|
||||
// Editor is the interface for launching an external text editor.
|
||||
type Editor interface {
|
||||
Edit(filename, initialValue string) (string, error)
|
||||
}
|
||||
|
||||
// UserEditor opens the user's preferred text editor for editing content.
|
||||
type UserEditor struct {
|
||||
IO *iostreams.IOStreams
|
||||
Config func() (gh.Config, error)
|
||||
}
|
||||
|
||||
// Edit opens the user's configured editor with the given filename and initial content, returning the edited result.
|
||||
func (e *UserEditor) Edit(filename, initialValue string) (string, error) {
|
||||
editorCommand, err := cmdutil.DetermineEditor(e.Config)
|
||||
if err != nil {
|
||||
|
|
@ -382,6 +402,7 @@ const editorHint = `
|
|||
Please Enter the title on the first line and the body on subsequent lines.
|
||||
Lines below dotted lines will be ignored, and an empty title aborts the creation process.`
|
||||
|
||||
// TitledEditSurvey returns a function that opens an editor for the user to provide a title and body.
|
||||
func TitledEditSurvey(editor Editor) func(string, string) (string, string, error) {
|
||||
return func(initialTitle, initialBody string) (string, string, error) {
|
||||
initialValue := strings.Join([]string{initialTitle, initialBody, editorHintMarker, editorHint}, "\n")
|
||||
|
|
@ -397,6 +418,7 @@ func TitledEditSurvey(editor Editor) func(string, string) (string, string, error
|
|||
}
|
||||
}
|
||||
|
||||
// InitEditorMode determines whether editor mode should be enabled based on flags and user configuration.
|
||||
func InitEditorMode(f *cmdutil.Factory, editorMode bool, webMode bool, canPrompt bool) (bool, error) {
|
||||
if err := cmdutil.MutuallyExclusive(
|
||||
"specify only one of `--editor` or `--web`",
|
||||
|
|
|
|||
|
|
@ -26,34 +26,42 @@ type pullRequestTemplate struct {
|
|||
Gbody string `graphql:"body"`
|
||||
}
|
||||
|
||||
// Name returns the display name of the issue template.
|
||||
func (t *issueTemplate) Name() string {
|
||||
return t.Gname
|
||||
}
|
||||
|
||||
// NameForSubmit returns the template name to include when submitting an issue.
|
||||
func (t *issueTemplate) NameForSubmit() string {
|
||||
return t.Gname
|
||||
}
|
||||
|
||||
// Body returns the content of the issue template as bytes.
|
||||
func (t *issueTemplate) Body() []byte {
|
||||
return []byte(t.Gbody)
|
||||
}
|
||||
|
||||
// Title returns the default title of the issue template.
|
||||
func (t *issueTemplate) Title() string {
|
||||
return t.Gtitle
|
||||
}
|
||||
|
||||
// Name returns the filename of the pull request template.
|
||||
func (t *pullRequestTemplate) Name() string {
|
||||
return t.Gname
|
||||
}
|
||||
|
||||
// NameForSubmit returns an empty string since pull request templates do not have a submission name.
|
||||
func (t *pullRequestTemplate) NameForSubmit() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Body returns the content of the pull request template as bytes.
|
||||
func (t *pullRequestTemplate) Body() []byte {
|
||||
return []byte(t.Gbody)
|
||||
}
|
||||
|
||||
// Title returns an empty string since pull request templates do not have a default title.
|
||||
func (t *pullRequestTemplate) Title() string {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -114,6 +122,7 @@ func listPullRequestTemplates(httpClient *http.Client, repo ghrepo.Interface) ([
|
|||
return templates, nil
|
||||
}
|
||||
|
||||
// Template defines the interface for issue and pull request templates.
|
||||
type Template interface {
|
||||
Name() string
|
||||
NameForSubmit() string
|
||||
|
|
@ -141,6 +150,7 @@ type templateManager struct {
|
|||
fetchError error
|
||||
}
|
||||
|
||||
// NewTemplateManager creates a templateManager that discovers issue or PR templates from the API and optionally the filesystem.
|
||||
func NewTemplateManager(httpClient *http.Client, repo ghrepo.Interface, p iprompter, dir string, allowFS bool, isPR bool) *templateManager {
|
||||
cachedClient := api.NewCachedHTTPClient(httpClient, time.Hour*24)
|
||||
return &templateManager{
|
||||
|
|
@ -167,6 +177,7 @@ func (m *templateManager) hasAPI() (bool, error) {
|
|||
return features.PullRequestTemplateQuery, nil
|
||||
}
|
||||
|
||||
// HasTemplates reports whether any templates are available for the repository.
|
||||
func (m *templateManager) HasTemplates() (bool, error) {
|
||||
if err := m.memoizedFetch(); err != nil {
|
||||
return false, err
|
||||
|
|
@ -174,6 +185,7 @@ func (m *templateManager) HasTemplates() (bool, error) {
|
|||
return len(m.templates) > 0, nil
|
||||
}
|
||||
|
||||
// LegacyBody returns the body of the legacy template, or nil if no legacy template exists.
|
||||
func (m *templateManager) LegacyBody() []byte {
|
||||
if m.legacyTemplate == nil {
|
||||
return nil
|
||||
|
|
@ -181,6 +193,7 @@ func (m *templateManager) LegacyBody() []byte {
|
|||
return m.legacyTemplate.Body()
|
||||
}
|
||||
|
||||
// Choose prompts the user to interactively select a template from the available options.
|
||||
func (m *templateManager) Choose() (Template, error) {
|
||||
if err := m.memoizedFetch(); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -210,6 +223,7 @@ func (m *templateManager) Choose() (Template, error) {
|
|||
return m.templates[selectedOption], nil
|
||||
}
|
||||
|
||||
// Select returns the template matching the given name, or an error if not found.
|
||||
func (m *templateManager) Select(name string) (Template, error) {
|
||||
if err := m.memoizedFetch(); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -294,18 +308,22 @@ type filesystemTemplate struct {
|
|||
path string
|
||||
}
|
||||
|
||||
// Name returns the display name extracted from the filesystem template file.
|
||||
func (t *filesystemTemplate) Name() string {
|
||||
return githubtemplate.ExtractName(t.path)
|
||||
}
|
||||
|
||||
// NameForSubmit returns an empty string since filesystem templates do not have a submission name.
|
||||
func (t *filesystemTemplate) NameForSubmit() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Body returns the contents of the filesystem template file.
|
||||
func (t *filesystemTemplate) Body() []byte {
|
||||
return githubtemplate.ExtractContents(t.path)
|
||||
}
|
||||
|
||||
// Title returns the title extracted from the filesystem template file.
|
||||
func (t *filesystemTemplate) Title() string {
|
||||
return githubtemplate.ExtractTitle(t.path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// StatusOptions holds the configuration for the pr status command.
|
||||
type StatusOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
GitClient *git.Client
|
||||
|
|
@ -39,6 +40,7 @@ type StatusOptions struct {
|
|||
Detector fd.Detector
|
||||
}
|
||||
|
||||
// NewCmdStatus creates the cobra command for showing the status of relevant pull requests.
|
||||
func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Command {
|
||||
opts := &StatusOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// UpdateBranchOptions holds the configuration for the pr update-branch command.
|
||||
type UpdateBranchOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
|
@ -27,6 +28,7 @@ type UpdateBranchOptions struct {
|
|||
Rebase bool
|
||||
}
|
||||
|
||||
// NewCmdUpdateBranch creates the cobra command for updating a pull request branch with the latest base branch changes.
|
||||
func NewCmdUpdateBranch(f *cmdutil.Factory, runF func(*UpdateBranchOptions) error) *cobra.Command {
|
||||
opts := &UpdateBranchOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ViewOptions holds the configuration for the pr view command.
|
||||
type ViewOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
Browser browser.Browser
|
||||
|
|
@ -37,6 +38,7 @@ type ViewOptions struct {
|
|||
Now func() time.Time
|
||||
}
|
||||
|
||||
// NewCmdView creates the cobra command for viewing a pull request's details.
|
||||
func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Command {
|
||||
opts := &ViewOptions{
|
||||
IO: f.IOStreams,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue