mirror of
https://github.com/abhinav/git-spice.git
synced 2026-08-31 07:47:47 +02:00
submit: Add --assign flag and assignees configuration (#951)
Add support for assigning change requests to users during submission with an `-a`/`--asign` flag for all submit commands. The accompanying `spice.submit.assignees` configuration option allows setting default assignees. LLM assistance: The first version of this change was made with ChatGPT Codex. The result was iterated upon significantly locally, both manually and with the help of Claude.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
kind: Added
|
||||
body: 'submit: Add --assign flag and ''spice.submit.assignees'' configuration option to assign CRs to users upon submission.'
|
||||
time: 2025-11-29T18:46:32.289707-08:00
|
||||
@@ -16,10 +16,7 @@ type submitOptions struct {
|
||||
NoWeb bool `help:"Alias for --web=false."`
|
||||
|
||||
// TODO: Other creation options e.g.:
|
||||
// - assignees
|
||||
// - labels
|
||||
// - milestone
|
||||
// - reviewers
|
||||
}
|
||||
|
||||
const _submitHelp = `
|
||||
|
||||
@@ -266,9 +266,10 @@ only if there are multiple CRs in the stack.
|
||||
* `-u`, `--[no-]update-only`: Only update existing change requests, do not create new ones
|
||||
* `-l`, `--label=LABEL,...`: Add labels to the change request. Pass multiple times or separate with commas.
|
||||
* `-r`, `--reviewer=REVIEWER,...`: Add reviewers to the change request. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag-hidden:{ title="Released in version" }</span><span class="mdx-badge__text">Unreleased</span>
|
||||
* `-a`, `--assign=ASSIGNEE,...`: Assign the change request to these users. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag-hidden:{ title="Released in version" }</span><span class="mdx-badge__text">Unreleased</span>
|
||||
* `--no-web`: Alias for --web=false.
|
||||
|
||||
**Configuration**: [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.label](/cli/config.md#spicesubmitlabel), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.updateOnly](/cli/config.md#spicesubmitupdateonly), [spice.submit.web](/cli/config.md#spicesubmitweb)
|
||||
**Configuration**: [spice.submit.assignees](/cli/config.md#spicesubmitassignees), [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.label](/cli/config.md#spicesubmitlabel), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.updateOnly](/cli/config.md#spicesubmitupdateonly), [spice.submit.web](/cli/config.md#spicesubmitweb)
|
||||
|
||||
### gs stack restack
|
||||
|
||||
@@ -387,10 +388,11 @@ only if there are multiple CRs in the stack.
|
||||
* `-u`, `--[no-]update-only`: Only update existing change requests, do not create new ones
|
||||
* `-l`, `--label=LABEL,...`: Add labels to the change request. Pass multiple times or separate with commas.
|
||||
* `-r`, `--reviewer=REVIEWER,...`: Add reviewers to the change request. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag-hidden:{ title="Released in version" }</span><span class="mdx-badge__text">Unreleased</span>
|
||||
* `-a`, `--assign=ASSIGNEE,...`: Assign the change request to these users. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag-hidden:{ title="Released in version" }</span><span class="mdx-badge__text">Unreleased</span>
|
||||
* `--no-web`: Alias for --web=false.
|
||||
* `--branch=NAME`: Branch to start at
|
||||
|
||||
**Configuration**: [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.label](/cli/config.md#spicesubmitlabel), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.updateOnly](/cli/config.md#spicesubmitupdateonly), [spice.submit.web](/cli/config.md#spicesubmitweb)
|
||||
**Configuration**: [spice.submit.assignees](/cli/config.md#spicesubmitassignees), [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.label](/cli/config.md#spicesubmitlabel), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.updateOnly](/cli/config.md#spicesubmitupdateonly), [spice.submit.web](/cli/config.md#spicesubmitweb)
|
||||
|
||||
### gs upstack restack
|
||||
|
||||
@@ -541,10 +543,11 @@ only if there are multiple CRs in the stack.
|
||||
* `-u`, `--[no-]update-only`: Only update existing change requests, do not create new ones
|
||||
* `-l`, `--label=LABEL,...`: Add labels to the change request. Pass multiple times or separate with commas.
|
||||
* `-r`, `--reviewer=REVIEWER,...`: Add reviewers to the change request. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag-hidden:{ title="Released in version" }</span><span class="mdx-badge__text">Unreleased</span>
|
||||
* `-a`, `--assign=ASSIGNEE,...`: Assign the change request to these users. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag-hidden:{ title="Released in version" }</span><span class="mdx-badge__text">Unreleased</span>
|
||||
* `--no-web`: Alias for --web=false.
|
||||
* `--branch=NAME`: Branch to start at
|
||||
|
||||
**Configuration**: [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.label](/cli/config.md#spicesubmitlabel), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.updateOnly](/cli/config.md#spicesubmitupdateonly), [spice.submit.web](/cli/config.md#spicesubmitweb)
|
||||
**Configuration**: [spice.submit.assignees](/cli/config.md#spicesubmitassignees), [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.label](/cli/config.md#spicesubmitlabel), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.updateOnly](/cli/config.md#spicesubmitupdateonly), [spice.submit.web](/cli/config.md#spicesubmitweb)
|
||||
|
||||
### gs downstack edit
|
||||
|
||||
@@ -995,12 +998,13 @@ only if there are multiple CRs in the stack.
|
||||
* `-u`, `--[no-]update-only`: Only update existing change requests, do not create new ones
|
||||
* `-l`, `--label=LABEL,...`: Add labels to the change request. Pass multiple times or separate with commas.
|
||||
* `-r`, `--reviewer=REVIEWER,...`: Add reviewers to the change request. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag-hidden:{ title="Released in version" }</span><span class="mdx-badge__text">Unreleased</span>
|
||||
* `-a`, `--assign=ASSIGNEE,...`: Assign the change request to these users. Pass multiple times or separate with commas. <span class="mdx-badge"><span class="mdx-badge__icon">:material-tag-hidden:{ title="Released in version" }</span><span class="mdx-badge__text">Unreleased</span>
|
||||
* `--no-web`: Alias for --web=false.
|
||||
* `--title=TITLE`: Title of the change request
|
||||
* `--body=BODY`: Body of the change request
|
||||
* `--branch=NAME`: Branch to submit
|
||||
|
||||
**Configuration**: [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.label](/cli/config.md#spicesubmitlabel), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.web](/cli/config.md#spicesubmitweb)
|
||||
**Configuration**: [spice.submit.assignees](/cli/config.md#spicesubmitassignees), [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.label](/cli/config.md#spicesubmitlabel), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.web](/cli/config.md#spicesubmitweb)
|
||||
|
||||
## Commit
|
||||
|
||||
|
||||
@@ -328,6 +328,18 @@ This option affects both interactive and non-interactive modes:
|
||||
- `true`: create CRs as drafts by default
|
||||
- `false` (default): create CRs as ready for review by default
|
||||
|
||||
### spice.submit.assignees
|
||||
|
||||
<!-- gs:version unreleased -->
|
||||
|
||||
Assign the configured users to all submitted and updated change requests
|
||||
when using $$gs branch submit$$ and friends.
|
||||
|
||||
The value must be a comma-separated list of usernames.
|
||||
|
||||
Assignees specified with the `--assign` flag
|
||||
will be combined with the configured assignees.
|
||||
|
||||
### spice.submit.label
|
||||
|
||||
<!-- gs:version v0.16.0 -->
|
||||
|
||||
@@ -261,6 +261,30 @@ will be combined with configured reviewers.
|
||||
When updating existing change requests,
|
||||
new reviewers are added to any existing reviewers on the CR.
|
||||
|
||||
## Assigning change requests
|
||||
|
||||
<!-- gs:version unreleased -->
|
||||
|
||||
You can assign change requests to specific users
|
||||
when submitting them with the `-a`/`--assign` flag.
|
||||
|
||||
```freeze language="terminal"
|
||||
{green}${reset} gs branch submit --assign alice --assign bob
|
||||
{green}INF{reset} Created #123: https://github.com/abhinav/git-spice/pull/123
|
||||
```
|
||||
|
||||
You can also configure default assignees
|
||||
with the $$spice.submit.assignees$$ configuration option.
|
||||
Assignees specified with the `--assign` flag
|
||||
will be combined with configured assignees.
|
||||
|
||||
```freeze language="terminal"
|
||||
{green}${reset} git config {red}spice.submit.assignees{reset} {mag}'alice,bob'{reset}
|
||||
```
|
||||
|
||||
When updating existing change requests,
|
||||
new assignees are added to any existing assignees on the CR.
|
||||
|
||||
## Importing open CRs
|
||||
|
||||
You can import an existing open CR into git-spice
|
||||
|
||||
@@ -305,6 +305,9 @@ type SubmitChangeRequest struct {
|
||||
|
||||
// Reviewers are optional reviewers to request reviews from.
|
||||
Reviewers []string
|
||||
|
||||
// Assignees are optional users to assign to the change.
|
||||
Assignees []string
|
||||
}
|
||||
|
||||
// SubmitChangeResult is the result of creating a new change in a repository.
|
||||
@@ -332,6 +335,9 @@ type EditChangeOptions struct {
|
||||
// Reviewers are the reviewers to request reviews from.
|
||||
// Existing reviewers associated with the change will not be modified.
|
||||
Reviewers []string
|
||||
|
||||
// Assignees are optional users to assign to the change.
|
||||
Assignees []string
|
||||
}
|
||||
|
||||
// FindChangeItem is a single result from searching for changes in the
|
||||
@@ -365,6 +371,10 @@ type FindChangeItem struct {
|
||||
// Reviewers are the usernames of users
|
||||
// who have been requested to review the change.
|
||||
Reviewers []string
|
||||
|
||||
// Assignees are the usernames of users
|
||||
// who are assigned to the change.
|
||||
Assignees []string
|
||||
}
|
||||
|
||||
// ChangeTemplate is a template for a new change proposal.
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/shurcooL/githubv4"
|
||||
)
|
||||
|
||||
func (r *Repository) addAssigneesToPullRequest(ctx context.Context, assignees []string, prGraphQLID githubv4.ID) error {
|
||||
if len(assignees) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
assigneeIDs, err := r.assigneeIDs(ctx, assignees)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get assignee IDs: %w", err)
|
||||
}
|
||||
|
||||
var m struct {
|
||||
AddAssigneesToAssignable struct {
|
||||
ClientMutationID githubv4.String `graphql:"clientMutationId"`
|
||||
} `graphql:"addAssigneesToAssignable(input: $input)"`
|
||||
}
|
||||
|
||||
input := githubv4.AddAssigneesToAssignableInput{
|
||||
AssignableID: prGraphQLID,
|
||||
AssigneeIDs: assigneeIDs,
|
||||
}
|
||||
|
||||
if err := r.client.Mutate(ctx, &m, input, nil); err != nil {
|
||||
return fmt.Errorf("add assignees to assignable: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// assigneeIDs resolves assignee logins to GitHub user IDs.
|
||||
// The returned slice may be shorter than the input
|
||||
// because duplicate logins are automatically deduplicated.
|
||||
func (r *Repository) assigneeIDs(ctx context.Context, assignees []string) ([]githubv4.ID, error) {
|
||||
ids := make([]githubv4.ID, 0, len(assignees))
|
||||
seen := make(map[string]struct{}, len(assignees))
|
||||
for _, assignee := range assignees {
|
||||
if _, ok := seen[assignee]; ok {
|
||||
continue
|
||||
}
|
||||
seen[assignee] = struct{}{}
|
||||
|
||||
id, err := r.assigneeID(ctx, assignee)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve assignee %q: %w", assignee, err)
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (r *Repository) assigneeID(ctx context.Context, login string) (githubv4.ID, error) {
|
||||
var q struct {
|
||||
User struct {
|
||||
ID githubv4.ID `graphql:"id"`
|
||||
} `graphql:"user(login: $login)"`
|
||||
}
|
||||
vars := map[string]any{
|
||||
"login": githubv4.String(login),
|
||||
}
|
||||
if err := r.client.Query(ctx, &q, vars); err != nil {
|
||||
return "", fmt.Errorf("query user: %w", err)
|
||||
}
|
||||
if q.User.ID == nil || q.User.ID == "" {
|
||||
return "", fmt.Errorf("user %q not found", login)
|
||||
}
|
||||
|
||||
return q.User.ID, nil
|
||||
}
|
||||
@@ -11,7 +11,11 @@ import (
|
||||
|
||||
// EditChange edits an existing change in a repository.
|
||||
func (r *Repository) EditChange(ctx context.Context, fid forge.ChangeID, opts forge.EditChangeOptions) error {
|
||||
if cmputil.Zero(opts.Base) && cmputil.Zero(opts.Draft) && len(opts.Labels) == 0 && len(opts.Reviewers) == 0 {
|
||||
if cmputil.Zero(opts.Base) &&
|
||||
cmputil.Zero(opts.Draft) &&
|
||||
len(opts.Labels) == 0 &&
|
||||
len(opts.Reviewers) == 0 &&
|
||||
len(opts.Assignees) == 0 {
|
||||
return nil // nothing to do
|
||||
}
|
||||
pr := mustPR(fid)
|
||||
@@ -85,15 +89,21 @@ func (r *Repository) EditChange(ctx context.Context, fid forge.ChangeID, opts fo
|
||||
r.log.Debug(logMsg, "pr", pr.Number)
|
||||
}
|
||||
|
||||
err = r.addLabelsToPullRequest(ctx, opts.Labels, graphQLID)
|
||||
if err != nil {
|
||||
// TODO:
|
||||
// perform in parallel, share resolved user IDs, etc.
|
||||
// maybe even cache and persist resolved IDs in store.
|
||||
|
||||
if err := r.addLabelsToPullRequest(ctx, opts.Labels, graphQLID); err != nil {
|
||||
return fmt.Errorf("add labels to PR: %w", err)
|
||||
}
|
||||
|
||||
err = r.addReviewersToPullRequest(ctx, opts.Reviewers, graphQLID)
|
||||
if err != nil {
|
||||
if err := r.addReviewersToPullRequest(ctx, opts.Reviewers, graphQLID); err != nil {
|
||||
return fmt.Errorf("add reviewers to PR: %w", err)
|
||||
}
|
||||
|
||||
if err := r.addAssigneesToPullRequest(ctx, opts.Assignees, graphQLID); err != nil {
|
||||
return fmt.Errorf("add assignees to PR: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -33,6 +33,11 @@ type findPRNode struct {
|
||||
} `graphql:"requestedReviewer"`
|
||||
} `graphql:"nodes"`
|
||||
} `graphql:"reviewRequests(first: 100)"`
|
||||
Assignees struct {
|
||||
Nodes []struct {
|
||||
Login githubv4.String `graphql:"login"`
|
||||
} `graphql:"nodes"`
|
||||
} `graphql:"assignees(first: 100)"`
|
||||
}
|
||||
|
||||
func (n *findPRNode) toFindChangeItem() *forge.FindChangeItem {
|
||||
@@ -52,6 +57,14 @@ func (n *findPRNode) toFindChangeItem() *forge.FindChangeItem {
|
||||
}
|
||||
}
|
||||
|
||||
var assignees []string
|
||||
if len(n.Assignees.Nodes) > 0 {
|
||||
assignees = make([]string, len(n.Assignees.Nodes))
|
||||
for i, node := range n.Assignees.Nodes {
|
||||
assignees[i] = string(node.Login)
|
||||
}
|
||||
}
|
||||
|
||||
return &forge.FindChangeItem{
|
||||
ID: &PR{
|
||||
Number: int(n.Number),
|
||||
@@ -65,6 +78,7 @@ func (n *findPRNode) toFindChangeItem() *forge.FindChangeItem {
|
||||
Draft: bool(n.IsDraft),
|
||||
Labels: labels,
|
||||
Reviewers: reviewers,
|
||||
Assignees: assignees,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1149,6 +1149,133 @@ func TestIntegration_Repository_FindChangeItem_WithReviewers(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegration_Repository_FindChangeItem_WithAssignees(t *testing.T) {
|
||||
branchFixture1 := fixturetest.New(_fixtures, "branch-no-assignees", func() string {
|
||||
return randomString(8)
|
||||
})
|
||||
branchFixture2 := fixturetest.New(_fixtures, "branch-with-assignees", func() string {
|
||||
return randomString(8)
|
||||
})
|
||||
|
||||
branchNoAssignees := branchFixture1.Get(t)
|
||||
branchWithAssignees := branchFixture2.Get(t)
|
||||
t.Logf("Creating branches: %s, %s", branchNoAssignees, branchWithAssignees)
|
||||
|
||||
var gitWork *git.Worktree
|
||||
if github.UpdateFixtures() {
|
||||
t.Setenv("GIT_AUTHOR_EMAIL", "bot@example.com")
|
||||
t.Setenv("GIT_AUTHOR_NAME", "gs-test[bot]")
|
||||
t.Setenv("GIT_COMMITTER_EMAIL", "bot@example.com")
|
||||
t.Setenv("GIT_COMMITTER_NAME", "gs-test[bot]")
|
||||
|
||||
output := t.Output()
|
||||
|
||||
t.Logf("Cloning test-repo...")
|
||||
repoDir := t.TempDir()
|
||||
cmd := exec.Command("git", "clone", "https://github.com/abhinav/test-repo", repoDir)
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
require.NoError(t, cmd.Run(), "failed to clone test-repo")
|
||||
|
||||
ctx := t.Context()
|
||||
|
||||
var err error
|
||||
gitWork, err = git.OpenWorktree(ctx, repoDir, git.OpenOptions{
|
||||
Log: silogtest.New(t),
|
||||
})
|
||||
require.NoError(t, err, "failed to open git repo")
|
||||
gitRepo := gitWork.Repository()
|
||||
|
||||
branches := []string{branchNoAssignees, branchWithAssignees}
|
||||
for _, branchName := range branches {
|
||||
require.NoError(t, gitRepo.CreateBranch(ctx, git.CreateBranchRequest{
|
||||
Name: branchName,
|
||||
}), "could not create branch: %s", branchName)
|
||||
require.NoError(t, gitWork.CheckoutBranch(ctx, branchName),
|
||||
"could not checkout branch: %s", branchName)
|
||||
require.NoError(t, os.WriteFile(
|
||||
filepath.Join(repoDir, branchName+".txt"),
|
||||
[]byte(randomString(32)),
|
||||
0o644,
|
||||
), "could not write file to branch")
|
||||
|
||||
cmd = exec.Command("git", "add", ".")
|
||||
cmd.Dir = repoDir
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
require.NoError(t, cmd.Run(), "git add failed")
|
||||
require.NoError(t, gitWork.Commit(ctx, git.CommitRequest{
|
||||
Message: "commit from test",
|
||||
}), "could not commit changes")
|
||||
|
||||
t.Logf("Pushing to origin")
|
||||
require.NoError(t,
|
||||
gitWork.Push(ctx, git.PushOptions{
|
||||
Remote: "origin",
|
||||
Refspec: git.Refspec(branchName),
|
||||
}), "error pushing branch")
|
||||
|
||||
t.Cleanup(func() {
|
||||
t.Logf("Deleting remote branch: %s", branchName)
|
||||
assert.NoError(t,
|
||||
gitWork.Push(context.WithoutCancel(ctx), git.PushOptions{
|
||||
Remote: "origin",
|
||||
Refspec: git.Refspec(":" + branchName),
|
||||
}), "error deleting branch")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
rec := newRecorder(t, t.Name())
|
||||
ghc := newGitHubClient(rec.GetDefaultClient())
|
||||
repo, err := github.NewRepository(
|
||||
t.Context(), new(github.Forge), "abhinav", "test-repo", silogtest.New(t), ghc, _testRepoID,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create PR without assignees.
|
||||
changeNoAssignees, err := repo.SubmitChange(t.Context(), forge.SubmitChangeRequest{
|
||||
Subject: branchNoAssignees,
|
||||
Body: "Test PR without assignees",
|
||||
Base: "main",
|
||||
Head: branchNoAssignees,
|
||||
})
|
||||
require.NoError(t, err, "error creating PR without assignees")
|
||||
|
||||
// Create PR with assignees.
|
||||
changeWithAssignees, err := repo.SubmitChange(t.Context(), forge.SubmitChangeRequest{
|
||||
Subject: branchWithAssignees,
|
||||
Body: "Test PR with assignees",
|
||||
Base: "main",
|
||||
Head: branchWithAssignees,
|
||||
Assignees: []string{"abhinav-robot"},
|
||||
})
|
||||
require.NoError(t, err, "error creating PR with assignees")
|
||||
|
||||
t.Run("NoAssignees", func(t *testing.T) {
|
||||
change, err := repo.FindChangeByID(t.Context(), changeNoAssignees.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, change.Assignees)
|
||||
|
||||
t.Run("AddAssignees", func(t *testing.T) {
|
||||
require.NoError(t,
|
||||
repo.EditChange(t.Context(), changeNoAssignees.ID, forge.EditChangeOptions{
|
||||
Assignees: []string{"abhinav-robot"},
|
||||
}), "could not add assignees to PR")
|
||||
|
||||
change, err := repo.FindChangeByID(t.Context(), changeNoAssignees.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"abhinav-robot"}, change.Assignees)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("WithAssignees", func(t *testing.T) {
|
||||
change, err := repo.FindChangeByID(t.Context(), changeWithAssignees.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"abhinav-robot"}, change.Assignees)
|
||||
})
|
||||
}
|
||||
|
||||
const _alnum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
// randomString generates a random alphanumeric string of length n.
|
||||
|
||||
@@ -66,11 +66,14 @@ func (r *Repository) SubmitChange(ctx context.Context, req forge.SubmitChangeReq
|
||||
return forge.SubmitChangeResult{}, fmt.Errorf("add labels to PR: %w", err)
|
||||
}
|
||||
|
||||
err = r.addReviewersToPullRequest(ctx, req.Reviewers, m.CreatePullRequest.PullRequest.ID)
|
||||
if err != nil {
|
||||
if err := r.addReviewersToPullRequest(ctx, req.Reviewers, m.CreatePullRequest.PullRequest.ID); err != nil {
|
||||
return forge.SubmitChangeResult{}, fmt.Errorf("add reviewers to PR: %w", err)
|
||||
}
|
||||
|
||||
if err := r.addAssigneesToPullRequest(ctx, req.Assignees, m.CreatePullRequest.PullRequest.ID); err != nil {
|
||||
return forge.SubmitChangeResult{}, fmt.Errorf("add assignees to PR: %w", err)
|
||||
}
|
||||
|
||||
return forge.SubmitChangeResult{
|
||||
ID: &PR{
|
||||
Number: int(m.CreatePullRequest.PullRequest.Number),
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
"wGXqQzcL"
|
||||
+1
@@ -0,0 +1 @@
|
||||
"eoRKB9gT"
|
||||
+1
-1
@@ -1 +1 @@
|
||||
"mjTKCPmd"
|
||||
"ANT1ebx1"
|
||||
+1
-1
@@ -1 +1 @@
|
||||
"T2aNl7RF"
|
||||
"zSQCqDRF"
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
"jh4ZQk3A"
|
||||
"wM4FFhyP"
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
"nzW86hKz"
|
||||
"O4Le0gYV"
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
"BZOypxxU"
|
||||
"9ce88Fjf"
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
"3oqpKcZs"
|
||||
"enu5hKhb"
|
||||
+1
-1
@@ -1 +1 @@
|
||||
"R1U4QZgt"
|
||||
"tZr8vasu"
|
||||
+7
-7
@@ -6,9 +6,9 @@ interactions:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 357
|
||||
content_length: 393
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}}}}}\",\"variables\":{\"number\":141,\"owner\":\"abhinav\",\"repo\":\"git-spice\"}}\n"
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}\",\"variables\":{\"number\":141,\"owner\":\"abhinav\",\"repo\":\"git-spice\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -20,21 +20,21 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOJ2BQKs5xNT-u\",\"number\":141,\"url\":\"https://github.com/abhinav/git-spice/pull/141\",\"title\":\"branch submit: Heal from external PR submissions\",\"state\":\"MERGED\",\"headRefOid\":\"df0289d83ffae816105947875db01c992224913d\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]}}}}}"
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOJ2BQKs5xNT-u\",\"number\":141,\"url\":\"https://github.com/abhinav/git-spice/pull/141\",\"title\":\"branch submit: Heal from external PR submissions\",\"state\":\"MERGED\",\"headRefOid\":\"df0289d83ffae816105947875db01c992224913d\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]},\"assignees\":{\"nodes\":[]}}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 603.244875ms
|
||||
duration: 493.643792ms
|
||||
- id: 1
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 357
|
||||
content_length: 393
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}}}}}\",\"variables\":{\"number\":999,\"owner\":\"abhinav\",\"repo\":\"git-spice\"}}\n"
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}\",\"variables\":{\"number\":999,\"owner\":\"abhinav\",\"repo\":\"git-spice\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -52,4 +52,4 @@ interactions:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 404.702708ms
|
||||
duration: 304.567458ms
|
||||
|
||||
internal/forge/github/testdata/fixtures/TestIntegration_Repository_FindChangeItem_WithAssignees.yaml
Vendored
+237
@@ -0,0 +1,237 @@
|
||||
---
|
||||
version: 2
|
||||
interactions:
|
||||
- id: 0
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 269
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:CreatePullRequestInput!){createPullRequest(input: $input){pullRequest{id,number,url}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"baseRefName\":\"main\",\"headRefName\":\"wGXqQzcL\",\"title\":\"wGXqQzcL\",\"body\":\"Test PR without assignees\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
url: https://api.github.com/graphql
|
||||
method: POST
|
||||
response:
|
||||
proto: HTTP/2.0
|
||||
proto_major: 2
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"createPullRequest\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5qF\",\"number\":29,\"url\":\"https://github.com/abhinav/test-repo/pull/29\"}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 1.112183375s
|
||||
- id: 1
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 266
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:CreatePullRequestInput!){createPullRequest(input: $input){pullRequest{id,number,url}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"baseRefName\":\"main\",\"headRefName\":\"eoRKB9gT\",\"title\":\"eoRKB9gT\",\"body\":\"Test PR with assignees\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
url: https://api.github.com/graphql
|
||||
method: POST
|
||||
response:
|
||||
proto: HTTP/2.0
|
||||
proto_major: 2
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"createPullRequest\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5qO\",\"number\":30,\"url\":\"https://github.com/abhinav/test-repo/pull/30\"}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 856.579917ms
|
||||
- id: 2
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 97
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($login:String!){user(login: $login){id}}\",\"variables\":{\"login\":\"abhinav-robot\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
url: https://api.github.com/graphql
|
||||
method: POST
|
||||
response:
|
||||
proto: HTTP/2.0
|
||||
proto_major: 2
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"user\":{\"id\":\"U_kgDOCzNVWQ\"}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 275.551458ms
|
||||
- id: 3
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 211
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:AddAssigneesToAssignableInput!){addAssigneesToAssignable(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"assignableId\":\"PR_kwDOMVd0xs62J5qO\",\"assigneeIds\":[\"U_kgDOCzNVWQ\"]}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
url: https://api.github.com/graphql
|
||||
method: POST
|
||||
response:
|
||||
proto: HTTP/2.0
|
||||
proto_major: 2
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"addAssigneesToAssignable\":{\"clientMutationId\":null}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 752.591917ms
|
||||
- id: 4
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 392
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}\",\"variables\":{\"number\":29,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
url: https://api.github.com/graphql
|
||||
method: POST
|
||||
response:
|
||||
proto: HTTP/2.0
|
||||
proto_major: 2
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5qF\",\"number\":29,\"url\":\"https://github.com/abhinav/test-repo/pull/29\",\"title\":\"wGXqQzcL\",\"state\":\"OPEN\",\"headRefOid\":\"afbc9f52aa456ecddc669e303c7986cc28822815\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]},\"assignees\":{\"nodes\":[]}}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 291.6925ms
|
||||
- id: 5
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 97
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($login:String!){user(login: $login){id}}\",\"variables\":{\"login\":\"abhinav-robot\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
url: https://api.github.com/graphql
|
||||
method: POST
|
||||
response:
|
||||
proto: HTTP/2.0
|
||||
proto_major: 2
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"user\":{\"id\":\"U_kgDOCzNVWQ\"}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 190.5495ms
|
||||
- id: 6
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 211
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:AddAssigneesToAssignableInput!){addAssigneesToAssignable(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"assignableId\":\"PR_kwDOMVd0xs62J5qF\",\"assigneeIds\":[\"U_kgDOCzNVWQ\"]}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
url: https://api.github.com/graphql
|
||||
method: POST
|
||||
response:
|
||||
proto: HTTP/2.0
|
||||
proto_major: 2
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"addAssigneesToAssignable\":{\"clientMutationId\":null}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 1.006603292s
|
||||
- id: 7
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 392
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}\",\"variables\":{\"number\":29,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
url: https://api.github.com/graphql
|
||||
method: POST
|
||||
response:
|
||||
proto: HTTP/2.0
|
||||
proto_major: 2
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5qF\",\"number\":29,\"url\":\"https://github.com/abhinav/test-repo/pull/29\",\"title\":\"wGXqQzcL\",\"state\":\"OPEN\",\"headRefOid\":\"afbc9f52aa456ecddc669e303c7986cc28822815\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]},\"assignees\":{\"nodes\":[{\"login\":\"abhinav-robot\"}]}}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 305.664584ms
|
||||
- id: 8
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 392
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}\",\"variables\":{\"number\":30,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
url: https://api.github.com/graphql
|
||||
method: POST
|
||||
response:
|
||||
proto: HTTP/2.0
|
||||
proto_major: 2
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5qO\",\"number\":30,\"url\":\"https://github.com/abhinav/test-repo/pull/30\",\"title\":\"eoRKB9gT\",\"state\":\"OPEN\",\"headRefOid\":\"3d086751e721e905e66c6cdb03204bef3cebf79a\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]},\"assignees\":{\"nodes\":[{\"login\":\"abhinav-robot\"}]}}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 305.994708ms
|
||||
internal/forge/github/testdata/fixtures/TestIntegration_Repository_FindChangeItem_WithReviewers.yaml
Vendored
+24
-24
@@ -8,7 +8,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 269
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:CreatePullRequestInput!){createPullRequest(input: $input){pullRequest{id,number,url}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"baseRefName\":\"main\",\"headRefName\":\"mjTKCPmd\",\"title\":\"mjTKCPmd\",\"body\":\"Test PR without reviewers\"}}}\n"
|
||||
body: "{\"query\":\"mutation($input:CreatePullRequestInput!){createPullRequest(input: $input){pullRequest{id,number,url}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"baseRefName\":\"main\",\"headRefName\":\"ANT1ebx1\",\"title\":\"ANT1ebx1\",\"body\":\"Test PR without reviewers\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -20,13 +20,13 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"createPullRequest\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6177e-\",\"number\":23,\"url\":\"https://github.com/abhinav/test-repo/pull/23\"}}}}"
|
||||
body: "{\"data\":{\"createPullRequest\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5lY\",\"number\":27,\"url\":\"https://github.com/abhinav/test-repo/pull/27\"}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 1.2415795s
|
||||
duration: 1.303241083s
|
||||
- id: 1
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -34,7 +34,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 266
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:CreatePullRequestInput!){createPullRequest(input: $input){pullRequest{id,number,url}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"baseRefName\":\"main\",\"headRefName\":\"T2aNl7RF\",\"title\":\"T2aNl7RF\",\"body\":\"Test PR with reviewers\"}}}\n"
|
||||
body: "{\"query\":\"mutation($input:CreatePullRequestInput!){createPullRequest(input: $input){pullRequest{id,number,url}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"baseRefName\":\"main\",\"headRefName\":\"zSQCqDRF\",\"title\":\"zSQCqDRF\",\"body\":\"Test PR with reviewers\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -46,13 +46,13 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"createPullRequest\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6177fW\",\"number\":24,\"url\":\"https://github.com/abhinav/test-repo/pull/24\"}}}}"
|
||||
body: "{\"data\":{\"createPullRequest\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5ln\",\"number\":28,\"url\":\"https://github.com/abhinav/test-repo/pull/28\"}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 1.022038042s
|
||||
duration: 925.301375ms
|
||||
- id: 2
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -78,7 +78,7 @@ interactions:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 211.413708ms
|
||||
duration: 224.945458ms
|
||||
- id: 3
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -86,7 +86,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 201
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:RequestReviewsInput!){requestReviews(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs6177fW\",\"userIds\":[\"U_kgDOCzNVWQ\"],\"union\":true}}}\n"
|
||||
body: "{\"query\":\"mutation($input:RequestReviewsInput!){requestReviews(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs62J5ln\",\"userIds\":[\"U_kgDOCzNVWQ\"],\"union\":true}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -104,15 +104,15 @@ interactions:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 703.874875ms
|
||||
duration: 592.370917ms
|
||||
- id: 4
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 356
|
||||
content_length: 392
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}}}}}\",\"variables\":{\"number\":23,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}\",\"variables\":{\"number\":27,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -124,13 +124,13 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6177e-\",\"number\":23,\"url\":\"https://github.com/abhinav/test-repo/pull/23\",\"title\":\"mjTKCPmd\",\"state\":\"OPEN\",\"headRefOid\":\"90706d61e9f37e34fbc2a6025fce678c21f80e36\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]}}}}}"
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5lY\",\"number\":27,\"url\":\"https://github.com/abhinav/test-repo/pull/27\",\"title\":\"ANT1ebx1\",\"state\":\"OPEN\",\"headRefOid\":\"1ccdb06986c11880298493465086f7fb3798cfd0\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]},\"assignees\":{\"nodes\":[]}}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 254.5145ms
|
||||
duration: 306.913417ms
|
||||
- id: 5
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -156,7 +156,7 @@ interactions:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 282.598583ms
|
||||
duration: 219.291542ms
|
||||
- id: 6
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -164,7 +164,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 201
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:RequestReviewsInput!){requestReviews(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs6177e-\",\"userIds\":[\"U_kgDOCzNVWQ\"],\"union\":true}}}\n"
|
||||
body: "{\"query\":\"mutation($input:RequestReviewsInput!){requestReviews(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs62J5lY\",\"userIds\":[\"U_kgDOCzNVWQ\"],\"union\":true}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -182,15 +182,15 @@ interactions:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 504.517292ms
|
||||
duration: 804.302333ms
|
||||
- id: 7
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 356
|
||||
content_length: 392
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}}}}}\",\"variables\":{\"number\":23,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}\",\"variables\":{\"number\":27,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -202,21 +202,21 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6177e-\",\"number\":23,\"url\":\"https://github.com/abhinav/test-repo/pull/23\",\"title\":\"mjTKCPmd\",\"state\":\"OPEN\",\"headRefOid\":\"90706d61e9f37e34fbc2a6025fce678c21f80e36\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[{\"requestedReviewer\":{\"login\":\"abhinav-robot\"}}]}}}}}"
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5lY\",\"number\":27,\"url\":\"https://github.com/abhinav/test-repo/pull/27\",\"title\":\"ANT1ebx1\",\"state\":\"OPEN\",\"headRefOid\":\"1ccdb06986c11880298493465086f7fb3798cfd0\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[{\"requestedReviewer\":{\"login\":\"abhinav-robot\"}}]},\"assignees\":{\"nodes\":[]}}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 342.989083ms
|
||||
duration: 499.657083ms
|
||||
- id: 8
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 356
|
||||
content_length: 392
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}}}}}\",\"variables\":{\"number\":24,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}\",\"variables\":{\"number\":28,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -228,10 +228,10 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6177fW\",\"number\":24,\"url\":\"https://github.com/abhinav/test-repo/pull/24\",\"title\":\"T2aNl7RF\",\"state\":\"OPEN\",\"headRefOid\":\"1f30682766458fb27448d9269d38de3d5bd6e5c8\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[{\"requestedReviewer\":{\"login\":\"abhinav-robot\"}}]}}}}}"
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5ln\",\"number\":28,\"url\":\"https://github.com/abhinav/test-repo/pull/28\",\"title\":\"zSQCqDRF\",\"state\":\"OPEN\",\"headRefOid\":\"0db58548d85dc0ab939dd7fcb6504631b272f5aa\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[{\"requestedReviewer\":{\"login\":\"abhinav-robot\"}}]},\"assignees\":{\"nodes\":[]}}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 247.697291ms
|
||||
duration: 318.846583ms
|
||||
|
||||
Vendored
+52
-52
@@ -8,7 +8,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 263
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:CreatePullRequestInput!){createPullRequest(input: $input){pullRequest{id,number,url}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"baseRefName\":\"main\",\"headRefName\":\"jh4ZQk3A\",\"title\":\"jh4ZQk3A\",\"body\":\"Test PR with labels\"}}}\n"
|
||||
body: "{\"query\":\"mutation($input:CreatePullRequestInput!){createPullRequest(input: $input){pullRequest{id,number,url}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"baseRefName\":\"main\",\"headRefName\":\"wM4FFhyP\",\"title\":\"wM4FFhyP\",\"body\":\"Test PR with labels\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -20,13 +20,13 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"createPullRequest\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6172xJ\",\"number\":22,\"url\":\"https://github.com/abhinav/test-repo/pull/22\"}}}}"
|
||||
body: "{\"data\":{\"createPullRequest\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5kl\",\"number\":26,\"url\":\"https://github.com/abhinav/test-repo/pull/26\"}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 941.031625ms
|
||||
duration: 957.111625ms
|
||||
- id: 1
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -34,7 +34,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 196
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($label:String!$name:String!$owner:String!){repository(owner: $owner, name: $name){label(name: $label){id}}}\",\"variables\":{\"label\":\"BZOypxxU\",\"name\":\"test-repo\",\"owner\":\"abhinav\"}}\n"
|
||||
body: "{\"query\":\"query($label:String!$name:String!$owner:String!){repository(owner: $owner, name: $name){label(name: $label){id}}}\",\"variables\":{\"label\":\"O4Le0gYV\",\"name\":\"test-repo\",\"owner\":\"abhinav\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -52,41 +52,15 @@ interactions:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 223.778292ms
|
||||
duration: 237.938459ms
|
||||
- id: 2
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 175
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:CreateLabelInput!){createLabel(input: $input){label{id}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"color\":\"EDEDED\",\"name\":\"BZOypxxU\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
url: https://api.github.com/graphql
|
||||
method: POST
|
||||
response:
|
||||
proto: HTTP/2.0
|
||||
proto_major: 2
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"createLabel\":{\"label\":{\"id\":\"LA_kwDOMVd0xs8AAAACQ_E5eA\"}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 388.993833ms
|
||||
- id: 3
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 196
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($label:String!$name:String!$owner:String!){repository(owner: $owner, name: $name){label(name: $label){id}}}\",\"variables\":{\"label\":\"nzW86hKz\",\"name\":\"test-repo\",\"owner\":\"abhinav\"}}\n"
|
||||
body: "{\"query\":\"query($label:String!$name:String!$owner:String!){repository(owner: $owner, name: $name){label(name: $label){id}}}\",\"variables\":{\"label\":\"9ce88Fjf\",\"name\":\"test-repo\",\"owner\":\"abhinav\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -104,7 +78,33 @@ interactions:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 716.4375ms
|
||||
duration: 261.647083ms
|
||||
- id: 3
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 175
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:CreateLabelInput!){createLabel(input: $input){label{id}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"color\":\"EDEDED\",\"name\":\"9ce88Fjf\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
url: https://api.github.com/graphql
|
||||
method: POST
|
||||
response:
|
||||
proto: HTTP/2.0
|
||||
proto_major: 2
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"createLabel\":{\"label\":{\"id\":\"LA_kwDOMVd0xs8AAAACRJ-tgQ\"}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 352.845ms
|
||||
- id: 4
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -112,7 +112,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 175
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:CreateLabelInput!){createLabel(input: $input){label{id}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"color\":\"EDEDED\",\"name\":\"nzW86hKz\"}}}\n"
|
||||
body: "{\"query\":\"mutation($input:CreateLabelInput!){createLabel(input: $input){label{id}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"color\":\"EDEDED\",\"name\":\"O4Le0gYV\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -124,13 +124,13 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"createLabel\":{\"label\":{\"id\":\"LA_kwDOMVd0xs8AAAACQ_E5kw\"}}}}"
|
||||
body: "{\"data\":{\"createLabel\":{\"label\":{\"id\":\"LA_kwDOMVd0xs8AAAACRJ-thA\"}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 326.201875ms
|
||||
duration: 508.449625ms
|
||||
- id: 5
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -138,7 +138,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 240
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:AddLabelsToLabelableInput!){addLabelsToLabelable(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"labelableId\":\"PR_kwDOMVd0xs6172xJ\",\"labelIds\":[\"LA_kwDOMVd0xs8AAAACQ_E5kw\",\"LA_kwDOMVd0xs8AAAACQ_E5eA\"]}}}\n"
|
||||
body: "{\"query\":\"mutation($input:AddLabelsToLabelableInput!){addLabelsToLabelable(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"labelableId\":\"PR_kwDOMVd0xs62J5kl\",\"labelIds\":[\"LA_kwDOMVd0xs8AAAACRJ-thA\",\"LA_kwDOMVd0xs8AAAACRJ-tgQ\"]}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -156,15 +156,15 @@ interactions:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 900.272375ms
|
||||
duration: 787.813667ms
|
||||
- id: 6
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 356
|
||||
content_length: 392
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}}}}}\",\"variables\":{\"number\":22,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}\",\"variables\":{\"number\":26,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -176,21 +176,21 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6172xJ\",\"number\":22,\"url\":\"https://github.com/abhinav/test-repo/pull/22\",\"title\":\"jh4ZQk3A\",\"state\":\"OPEN\",\"headRefOid\":\"e9951fa08320f2712a76da385d3452d50703eee3\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[{\"name\":\"BZOypxxU\"},{\"name\":\"nzW86hKz\"}]},\"reviewRequests\":{\"nodes\":[]}}}}}"
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5kl\",\"number\":26,\"url\":\"https://github.com/abhinav/test-repo/pull/26\",\"title\":\"wM4FFhyP\",\"state\":\"OPEN\",\"headRefOid\":\"5b6b9dfe4526f7dfbb98bc9f68a4143b3df2ee6e\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[{\"name\":\"9ce88Fjf\"},{\"name\":\"O4Le0gYV\"}]},\"reviewRequests\":{\"nodes\":[]},\"assignees\":{\"nodes\":[]}}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 307.5605ms
|
||||
duration: 305.513958ms
|
||||
- id: 7
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 545
|
||||
content_length: 581
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($branch:String!$limit:Int!$owner:String!$repo:String!$states:[PullRequestState!]!){repository(owner: $owner, name: $repo){pullRequests(first: $limit, headRefName: $branch, states: $states, orderBy: {field: UPDATED_AT, direction: DESC}){nodes{id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}}}}}}\",\"variables\":{\"branch\":\"jh4ZQk3A\",\"limit\":10,\"owner\":\"abhinav\",\"repo\":\"test-repo\",\"states\":[\"OPEN\",\"CLOSED\",\"MERGED\"]}}\n"
|
||||
body: "{\"query\":\"query($branch:String!$limit:Int!$owner:String!$repo:String!$states:[PullRequestState!]!){repository(owner: $owner, name: $repo){pullRequests(first: $limit, headRefName: $branch, states: $states, orderBy: {field: UPDATED_AT, direction: DESC}){nodes{id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}}\",\"variables\":{\"branch\":\"wM4FFhyP\",\"limit\":10,\"owner\":\"abhinav\",\"repo\":\"test-repo\",\"states\":[\"OPEN\",\"CLOSED\",\"MERGED\"]}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -202,13 +202,13 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequests\":{\"nodes\":[{\"id\":\"PR_kwDOMVd0xs6172xJ\",\"number\":22,\"url\":\"https://github.com/abhinav/test-repo/pull/22\",\"title\":\"jh4ZQk3A\",\"state\":\"OPEN\",\"headRefOid\":\"e9951fa08320f2712a76da385d3452d50703eee3\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[{\"name\":\"BZOypxxU\"},{\"name\":\"nzW86hKz\"}]},\"reviewRequests\":{\"nodes\":[]}}]}}}}"
|
||||
body: "{\"data\":{\"repository\":{\"pullRequests\":{\"nodes\":[{\"id\":\"PR_kwDOMVd0xs62J5kl\",\"number\":26,\"url\":\"https://github.com/abhinav/test-repo/pull/26\",\"title\":\"wM4FFhyP\",\"state\":\"OPEN\",\"headRefOid\":\"5b6b9dfe4526f7dfbb98bc9f68a4143b3df2ee6e\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[{\"name\":\"9ce88Fjf\"},{\"name\":\"O4Le0gYV\"}]},\"reviewRequests\":{\"nodes\":[]},\"assignees\":{\"nodes\":[]}}]}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 299.002ms
|
||||
duration: 306.049042ms
|
||||
- id: 8
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -216,7 +216,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 133
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:DeleteLabelInput!){deleteLabel(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"id\":\"nzW86hKz\"}}}\n"
|
||||
body: "{\"query\":\"mutation($input:DeleteLabelInput!){deleteLabel(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"id\":\"O4Le0gYV\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -228,13 +228,13 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"deleteLabel\":null},\"errors\":[{\"type\":\"NOT_FOUND\",\"path\":[\"deleteLabel\"],\"locations\":[{\"line\":1,\"column\":36}],\"message\":\"Could not resolve to a node with the global id of 'nzW86hKz'\"}]}"
|
||||
body: "{\"data\":{\"deleteLabel\":null},\"errors\":[{\"type\":\"NOT_FOUND\",\"path\":[\"deleteLabel\"],\"locations\":[{\"line\":1,\"column\":36}],\"message\":\"Could not resolve to a node with the global id of 'O4Le0gYV'\"}]}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 257.160584ms
|
||||
duration: 193.504583ms
|
||||
- id: 9
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -242,7 +242,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 133
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:DeleteLabelInput!){deleteLabel(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"id\":\"BZOypxxU\"}}}\n"
|
||||
body: "{\"query\":\"mutation($input:DeleteLabelInput!){deleteLabel(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"id\":\"9ce88Fjf\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -254,10 +254,10 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"deleteLabel\":null},\"errors\":[{\"type\":\"NOT_FOUND\",\"path\":[\"deleteLabel\"],\"locations\":[{\"line\":1,\"column\":36}],\"message\":\"Could not resolve to a node with the global id of 'BZOypxxU'\"}]}"
|
||||
body: "{\"data\":{\"deleteLabel\":null},\"errors\":[{\"type\":\"NOT_FOUND\",\"path\":[\"deleteLabel\"],\"locations\":[{\"line\":1,\"column\":36}],\"message\":\"Could not resolve to a node with the global id of '9ce88Fjf'\"}]}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 244.761625ms
|
||||
duration: 193.423083ms
|
||||
|
||||
Vendored
+7
-7
@@ -6,9 +6,9 @@ interactions:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 547
|
||||
content_length: 583
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($branch:String!$limit:Int!$owner:String!$repo:String!$states:[PullRequestState!]!){repository(owner: $owner, name: $repo){pullRequests(first: $limit, headRefName: $branch, states: $states, orderBy: {field: UPDATED_AT, direction: DESC}){nodes{id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}}}}}}\",\"variables\":{\"branch\":\"gh-graphql\",\"limit\":10,\"owner\":\"abhinav\",\"repo\":\"git-spice\",\"states\":[\"OPEN\",\"CLOSED\",\"MERGED\"]}}\n"
|
||||
body: "{\"query\":\"query($branch:String!$limit:Int!$owner:String!$repo:String!$states:[PullRequestState!]!){repository(owner: $owner, name: $repo){pullRequests(first: $limit, headRefName: $branch, states: $states, orderBy: {field: UPDATED_AT, direction: DESC}){nodes{id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}}\",\"variables\":{\"branch\":\"gh-graphql\",\"limit\":10,\"owner\":\"abhinav\",\"repo\":\"git-spice\",\"states\":[\"OPEN\",\"CLOSED\",\"MERGED\"]}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -20,21 +20,21 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequests\":{\"nodes\":[{\"id\":\"PR_kwDOJ2BQKs5xNeqO\",\"number\":144,\"url\":\"https://github.com/abhinav/git-spice/pull/144\",\"title\":\"GitHub: Use GraphQL API\",\"state\":\"MERGED\",\"headRefOid\":\"5d74cecfe3cb066044d129232229e07f5d04e194\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]}}]}}}}"
|
||||
body: "{\"data\":{\"repository\":{\"pullRequests\":{\"nodes\":[{\"id\":\"PR_kwDOJ2BQKs5xNeqO\",\"number\":144,\"url\":\"https://github.com/abhinav/git-spice/pull/144\",\"title\":\"GitHub: Use GraphQL API\",\"state\":\"MERGED\",\"headRefOid\":\"5d74cecfe3cb066044d129232229e07f5d04e194\",\"baseRefName\":\"main\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]},\"assignees\":{\"nodes\":[]}}]}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 308.087875ms
|
||||
duration: 301.692167ms
|
||||
- id: 1
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 551
|
||||
content_length: 587
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($branch:String!$limit:Int!$owner:String!$repo:String!$states:[PullRequestState!]!){repository(owner: $owner, name: $repo){pullRequests(first: $limit, headRefName: $branch, states: $states, orderBy: {field: UPDATED_AT, direction: DESC}){nodes{id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}}}}}}\",\"variables\":{\"branch\":\"does-not-exist\",\"limit\":10,\"owner\":\"abhinav\",\"repo\":\"git-spice\",\"states\":[\"OPEN\",\"CLOSED\",\"MERGED\"]}}\n"
|
||||
body: "{\"query\":\"query($branch:String!$limit:Int!$owner:String!$repo:String!$states:[PullRequestState!]!){repository(owner: $owner, name: $repo){pullRequests(first: $limit, headRefName: $branch, states: $states, orderBy: {field: UPDATED_AT, direction: DESC}){nodes{id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}}\",\"variables\":{\"branch\":\"does-not-exist\",\"limit\":10,\"owner\":\"abhinav\",\"repo\":\"git-spice\",\"states\":[\"OPEN\",\"CLOSED\",\"MERGED\"]}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -52,4 +52,4 @@ interactions:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 283.615917ms
|
||||
duration: 306.654ms
|
||||
|
||||
+21
-21
@@ -8,7 +8,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 251
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:CreatePullRequestInput!){createPullRequest(input: $input){pullRequest{id,number,url}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"baseRefName\":\"main\",\"headRefName\":\"R1U4QZgt\",\"title\":\"R1U4QZgt\",\"body\":\"Test PR\"}}}\n"
|
||||
body: "{\"query\":\"mutation($input:CreatePullRequestInput!){createPullRequest(input: $input){pullRequest{id,number,url}}}\",\"variables\":{\"input\":{\"repositoryId\":\"R_kgDOMVd0xg\",\"baseRefName\":\"main\",\"headRefName\":\"tZr8vasu\",\"title\":\"tZr8vasu\",\"body\":\"Test PR\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -20,13 +20,13 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"createPullRequest\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6172q_\",\"number\":21,\"url\":\"https://github.com/abhinav/test-repo/pull/21\"}}}}"
|
||||
body: "{\"data\":{\"createPullRequest\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5js\",\"number\":25,\"url\":\"https://github.com/abhinav/test-repo/pull/25\"}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 1.1529645s
|
||||
duration: 803.254792ms
|
||||
- id: 1
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -34,7 +34,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 192
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:UpdatePullRequestInput!){updatePullRequest(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs6172q_\",\"baseRefName\":\"3oqpKcZs\"}}}\n"
|
||||
body: "{\"query\":\"mutation($input:UpdatePullRequestInput!){updatePullRequest(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs62J5js\",\"baseRefName\":\"enu5hKhb\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -52,15 +52,15 @@ interactions:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 910.070583ms
|
||||
duration: 1.4191675s
|
||||
- id: 2
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 356
|
||||
content_length: 392
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}}}}}\",\"variables\":{\"number\":21,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}\",\"variables\":{\"number\":25,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -72,13 +72,13 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6172q_\",\"number\":21,\"url\":\"https://github.com/abhinav/test-repo/pull/21\",\"title\":\"R1U4QZgt\",\"state\":\"OPEN\",\"headRefOid\":\"3b96e8fcb67c26d4dfbdc208bcfc782a70b8cb11\",\"baseRefName\":\"3oqpKcZs\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]}}}}}"
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5js\",\"number\":25,\"url\":\"https://github.com/abhinav/test-repo/pull/25\",\"title\":\"tZr8vasu\",\"state\":\"OPEN\",\"headRefOid\":\"a12069ffae274a48b74b7007cab875063a117f21\",\"baseRefName\":\"enu5hKhb\",\"isDraft\":false,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]},\"assignees\":{\"nodes\":[]}}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 305.829042ms
|
||||
duration: 305.763167ms
|
||||
- id: 3
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -86,7 +86,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 188
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:UpdatePullRequestInput!){updatePullRequest(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs6172q_\",\"baseRefName\":\"main\"}}}\n"
|
||||
body: "{\"query\":\"mutation($input:UpdatePullRequestInput!){updatePullRequest(input: $input){clientMutationId}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs62J5js\",\"baseRefName\":\"main\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -104,7 +104,7 @@ interactions:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 818.162625ms
|
||||
duration: 818.21425ms
|
||||
- id: 4
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -112,7 +112,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 182
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:ConvertPullRequestToDraftInput!){convertPullRequestToDraft(input: $input){pullRequest{id}}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs6172q_\"}}}\n"
|
||||
body: "{\"query\":\"mutation($input:ConvertPullRequestToDraftInput!){convertPullRequestToDraft(input: $input){pullRequest{id}}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs62J5js\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -124,21 +124,21 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"convertPullRequestToDraft\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6172q_\"}}}}"
|
||||
body: "{\"data\":{\"convertPullRequestToDraft\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5js\"}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 1.628361541s
|
||||
duration: 502.142334ms
|
||||
- id: 5
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
proto_major: 1
|
||||
proto_minor: 1
|
||||
content_length: 356
|
||||
content_length: 392
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}}}}}\",\"variables\":{\"number\":21,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
body: "{\"query\":\"query($number:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){pullRequest(number: $number){id,number,url,title,state,headRefOid,baseRefName,isDraft,labels(first: 100){nodes{name}},reviewRequests(first: 100){nodes{requestedReviewer{... on Actor{login}}}},assignees(first: 100){nodes{login}}}}}\",\"variables\":{\"number\":25,\"owner\":\"abhinav\",\"repo\":\"test-repo\"}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -150,13 +150,13 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6172q_\",\"number\":21,\"url\":\"https://github.com/abhinav/test-repo/pull/21\",\"title\":\"R1U4QZgt\",\"state\":\"OPEN\",\"headRefOid\":\"3b96e8fcb67c26d4dfbdc208bcfc782a70b8cb11\",\"baseRefName\":\"main\",\"isDraft\":true,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]}}}}}"
|
||||
body: "{\"data\":{\"repository\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5js\",\"number\":25,\"url\":\"https://github.com/abhinav/test-repo/pull/25\",\"title\":\"tZr8vasu\",\"state\":\"OPEN\",\"headRefOid\":\"a12069ffae274a48b74b7007cab875063a117f21\",\"baseRefName\":\"main\",\"isDraft\":true,\"labels\":{\"nodes\":[]},\"reviewRequests\":{\"nodes\":[]},\"assignees\":{\"nodes\":[]}}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 305.79875ms
|
||||
duration: 306.089416ms
|
||||
- id: 6
|
||||
request:
|
||||
proto: HTTP/1.1
|
||||
@@ -164,7 +164,7 @@ interactions:
|
||||
proto_minor: 1
|
||||
content_length: 190
|
||||
host: api.github.com
|
||||
body: "{\"query\":\"mutation($input:MarkPullRequestReadyForReviewInput!){markPullRequestReadyForReview(input: $input){pullRequest{id}}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs6172q_\"}}}\n"
|
||||
body: "{\"query\":\"mutation($input:MarkPullRequestReadyForReviewInput!){markPullRequestReadyForReview(input: $input){pullRequest{id}}}\",\"variables\":{\"input\":{\"pullRequestId\":\"PR_kwDOMVd0xs62J5js\"}}}\n"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json
|
||||
@@ -176,10 +176,10 @@ interactions:
|
||||
proto_minor: 0
|
||||
content_length: -1
|
||||
uncompressed: true
|
||||
body: "{\"data\":{\"markPullRequestReadyForReview\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs6172q_\"}}}}"
|
||||
body: "{\"data\":{\"markPullRequestReadyForReview\":{\"pullRequest\":{\"id\":\"PR_kwDOMVd0xs62J5js\"}}}}"
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
status: 200 OK
|
||||
code: 200
|
||||
duration: 818.4605ms
|
||||
duration: 613.043625ms
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package gitlab
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
gitlab "gitlab.com/gitlab-org/api/client-go"
|
||||
)
|
||||
|
||||
// assigneeIDs resolves assignee usernames to GitLab user IDs.
|
||||
// The returned slice may be shorter than the input
|
||||
// because duplicate usernames are automatically deduplicated.
|
||||
func (r *Repository) assigneeIDs(ctx context.Context, assignees []string) ([]int, error) {
|
||||
ids := make([]int, 0, len(assignees))
|
||||
seen := make(map[string]struct{}, len(assignees))
|
||||
for _, assignee := range assignees {
|
||||
if _, ok := seen[assignee]; ok {
|
||||
continue
|
||||
}
|
||||
seen[assignee] = struct{}{}
|
||||
|
||||
id, err := r.assigneeID(ctx, assignee)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve assignee %q: %w", assignee, err)
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (r *Repository) assigneeID(ctx context.Context, username string) (int, error) {
|
||||
users, _, err := r.client.Users.ListUsers(&gitlab.ListUsersOptions{
|
||||
Username: gitlab.Ptr(username),
|
||||
}, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("list users: %w", err)
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return 0, fmt.Errorf("user %q not found", username)
|
||||
}
|
||||
|
||||
id := users[0].ID
|
||||
return id, nil
|
||||
}
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"regexp"
|
||||
"slices"
|
||||
|
||||
gitlab "gitlab.com/gitlab-org/api/client-go"
|
||||
"go.abhg.dev/gs/internal/cmputil"
|
||||
@@ -19,28 +21,52 @@ var _draftRegex = regexp.MustCompile(`(?i)^\s*(\[Draft]|Draft:|\(Draft\))\s*`)
|
||||
|
||||
// EditChange edits an existing change in a repository.
|
||||
func (r *Repository) EditChange(ctx context.Context, id forge.ChangeID, opts forge.EditChangeOptions) error {
|
||||
if cmputil.Zero(opts.Base) && cmputil.Zero(opts.Draft) && len(opts.Labels) == 0 && len(opts.Reviewers) == 0 {
|
||||
if cmputil.Zero(opts.Base) &&
|
||||
cmputil.Zero(opts.Draft) &&
|
||||
len(opts.Labels) == 0 &&
|
||||
len(opts.Reviewers) == 0 &&
|
||||
len(opts.Assignees) == 0 {
|
||||
return nil // nothing to do
|
||||
}
|
||||
|
||||
mr := mustMR(id)
|
||||
mrID := mustMR(id)
|
||||
|
||||
var (
|
||||
updateOptions gitlab.UpdateMergeRequestOptions
|
||||
logUpdates []slog.Attr
|
||||
|
||||
mergeRequest *gitlab.MergeRequest
|
||||
)
|
||||
|
||||
// Used if we need to fetch the current MR status.
|
||||
getMergeRequest := func() (*gitlab.MergeRequest, error) {
|
||||
if mergeRequest != nil {
|
||||
return mergeRequest, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
mergeRequest, _, err = r.client.MergeRequests.GetMergeRequest(
|
||||
r.repoID, mrID.Number, nil,
|
||||
gitlab.WithContext(ctx),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get merge request for update: %w", err)
|
||||
}
|
||||
|
||||
return mergeRequest, nil
|
||||
}
|
||||
if opts.Base != "" {
|
||||
updateOptions.TargetBranch = &opts.Base
|
||||
logUpdates = append(logUpdates, slog.String("base", opts.Base))
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// As part of submit, we've likely already fetched this information.
|
||||
// Cache it in memory.
|
||||
if opts.Draft != nil {
|
||||
mr, _, err := r.client.MergeRequests.GetMergeRequest(
|
||||
r.repoID, mr.Number, nil,
|
||||
gitlab.WithContext(ctx),
|
||||
)
|
||||
mr, err := getMergeRequest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get merge request for update: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if *opts.Draft {
|
||||
@@ -65,12 +91,35 @@ func (r *Repository) EditChange(ctx context.Context, id forge.ChangeID, opts for
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolve reviewer IDs: %w", err)
|
||||
}
|
||||
updateOptions.ReviewerIDs = &reviewerIDs
|
||||
logUpdates = append(logUpdates, slog.Any("reviewers", opts.Reviewers))
|
||||
|
||||
mr, err := getMergeRequest()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
merged := mergeAssigneeIDs(mr.Reviewers, reviewerIDs)
|
||||
updateOptions.ReviewerIDs = &merged
|
||||
logUpdates = append(logUpdates, slog.Any("reviewers", merged))
|
||||
}
|
||||
|
||||
if len(opts.Assignees) > 0 {
|
||||
assigneeIDs, err := r.assigneeIDs(ctx, opts.Assignees)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolve assignees: %w", err)
|
||||
}
|
||||
|
||||
mr, err := getMergeRequest()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
merged := mergeAssigneeIDs(mr.Assignees, assigneeIDs)
|
||||
updateOptions.AssigneeIDs = &merged
|
||||
logUpdates = append(logUpdates, slog.Any("assignees", merged))
|
||||
}
|
||||
|
||||
_, _, err := r.client.MergeRequests.UpdateMergeRequest(
|
||||
r.repoID, mr.Number, &updateOptions,
|
||||
r.repoID, mrID.Number, &updateOptions,
|
||||
gitlab.WithContext(ctx),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -78,10 +127,21 @@ func (r *Repository) EditChange(ctx context.Context, id forge.ChangeID, opts for
|
||||
}
|
||||
if len(logUpdates) > 0 {
|
||||
r.log.Debug("Updated merge request",
|
||||
"mr", mr.Number,
|
||||
"mr", mrID.Number,
|
||||
"new", slog.GroupValue(logUpdates...),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeAssigneeIDs(existing []*gitlab.BasicUser, assignees []int) []int {
|
||||
seen := make(map[int]struct{}, len(existing)+len(assignees))
|
||||
for _, user := range existing {
|
||||
seen[user.ID] = struct{}{}
|
||||
}
|
||||
for _, id := range assignees {
|
||||
seen[id] = struct{}{}
|
||||
}
|
||||
return slices.Sorted(maps.Keys(seen))
|
||||
}
|
||||
|
||||
@@ -23,6 +23,14 @@ func basicMergeRequestToFindChangeItem(mr *gitlab.BasicMergeRequest) *forge.Find
|
||||
}
|
||||
}
|
||||
|
||||
var assignees []string
|
||||
if len(mr.Assignees) > 0 {
|
||||
assignees = make([]string, len(mr.Assignees))
|
||||
for i, assignee := range mr.Assignees {
|
||||
assignees[i] = assignee.Username
|
||||
}
|
||||
}
|
||||
|
||||
return &forge.FindChangeItem{
|
||||
ID: &MR{
|
||||
Number: mr.IID,
|
||||
@@ -35,6 +43,7 @@ func basicMergeRequestToFindChangeItem(mr *gitlab.BasicMergeRequest) *forge.Find
|
||||
Draft: mr.Draft,
|
||||
Labels: labels,
|
||||
Reviewers: reviewers,
|
||||
Assignees: assignees,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +61,14 @@ func mergeRequestToFindChangeItem(mr *gitlab.MergeRequest) *forge.FindChangeItem
|
||||
}
|
||||
}
|
||||
|
||||
var assignees []string
|
||||
if len(mr.Assignees) > 0 {
|
||||
assignees = make([]string, len(mr.Assignees))
|
||||
for i, assignee := range mr.Assignees {
|
||||
assignees[i] = assignee.Username
|
||||
}
|
||||
}
|
||||
|
||||
return &forge.FindChangeItem{
|
||||
ID: &MR{
|
||||
Number: mr.IID,
|
||||
@@ -64,6 +81,7 @@ func mergeRequestToFindChangeItem(mr *gitlab.MergeRequest) *forge.FindChangeItem
|
||||
Draft: mr.Draft,
|
||||
Labels: labels,
|
||||
Reviewers: reviewers,
|
||||
Assignees: assignees,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1082,6 +1082,169 @@ func TestIntegration_Repository_notFoundError(t *testing.T) {
|
||||
assert.ErrorContains(t, err, "404 Not Found")
|
||||
}
|
||||
|
||||
func TestIntegration_Repository_FindChangeItem_WithAssignees(t *testing.T) {
|
||||
branchFixture1 := fixturetest.New(_fixtures, "branch-no-assignees", func() string {
|
||||
return randomString(8)
|
||||
})
|
||||
branchFixture2 := fixturetest.New(_fixtures, "branch-with-assignees", func() string {
|
||||
return randomString(8)
|
||||
})
|
||||
|
||||
branchNoAssignees := branchFixture1.Get(t)
|
||||
branchWithAssignees := branchFixture2.Get(t)
|
||||
t.Logf("Creating branches: %s, %s", branchNoAssignees, branchWithAssignees)
|
||||
|
||||
var (
|
||||
gitRepo *git.Repository // only when _update is true
|
||||
gitWork *git.Worktree
|
||||
)
|
||||
if gitlab.UpdateFixtures() {
|
||||
t.Setenv("GIT_AUTHOR_EMAIL", "bot@example.com")
|
||||
t.Setenv("GIT_AUTHOR_NAME", "gs-test[bot]")
|
||||
t.Setenv("GIT_COMMITTER_EMAIL", "bot@example.com")
|
||||
t.Setenv("GIT_COMMITTER_NAME", "gs-test[bot]")
|
||||
|
||||
output := t.Output()
|
||||
|
||||
ctx := t.Context()
|
||||
|
||||
t.Logf("Cloning test-repo...")
|
||||
repoDir := t.TempDir()
|
||||
cmd := exec.Command("git", "clone", "git@gitlab.com:abg/test-repo.git", repoDir)
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
require.NoError(t, cmd.Run(), "failed to clone test-repo")
|
||||
|
||||
var err error
|
||||
gitWork, err = git.OpenWorktree(ctx, repoDir, git.OpenOptions{
|
||||
Log: silogtest.New(t),
|
||||
})
|
||||
require.NoError(t, err, "failed to open git repo")
|
||||
gitRepo = gitWork.Repository()
|
||||
|
||||
// Create and push first branch (no assignees).
|
||||
require.NoError(t, gitRepo.CreateBranch(ctx, git.CreateBranchRequest{
|
||||
Name: branchNoAssignees,
|
||||
}), "could not create branch: %s", branchNoAssignees)
|
||||
require.NoError(t, gitWork.CheckoutBranch(ctx, branchNoAssignees),
|
||||
"could not checkout branch: %s", branchNoAssignees)
|
||||
require.NoError(t, os.WriteFile(
|
||||
filepath.Join(repoDir, branchNoAssignees+".txt"),
|
||||
[]byte(randomString(32)),
|
||||
0o644,
|
||||
), "could not write file to branch")
|
||||
|
||||
cmd = exec.Command("git", "add", ".")
|
||||
cmd.Dir = repoDir
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
require.NoError(t, cmd.Run(), "git add failed")
|
||||
require.NoError(t, gitWork.Commit(ctx, git.CommitRequest{
|
||||
Message: "commit from test",
|
||||
}), "could not commit changes")
|
||||
|
||||
t.Logf("Pushing to origin: %s", branchNoAssignees)
|
||||
require.NoError(t,
|
||||
gitWork.Push(ctx, git.PushOptions{
|
||||
Remote: "origin",
|
||||
Refspec: git.Refspec(branchNoAssignees),
|
||||
}), "error pushing branch")
|
||||
|
||||
// Create and push second branch (with assignees).
|
||||
require.NoError(t, gitWork.CheckoutBranch(ctx, "main"),
|
||||
"could not checkout main branch")
|
||||
require.NoError(t, gitRepo.CreateBranch(ctx, git.CreateBranchRequest{
|
||||
Name: branchWithAssignees,
|
||||
}), "could not create branch: %s", branchWithAssignees)
|
||||
require.NoError(t, gitWork.CheckoutBranch(ctx, branchWithAssignees),
|
||||
"could not checkout branch: %s", branchWithAssignees)
|
||||
require.NoError(t, os.WriteFile(
|
||||
filepath.Join(repoDir, branchWithAssignees+".txt"),
|
||||
[]byte(randomString(32)),
|
||||
0o644,
|
||||
), "could not write file to branch")
|
||||
|
||||
cmd = exec.Command("git", "add", ".")
|
||||
cmd.Dir = repoDir
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
require.NoError(t, cmd.Run(), "git add failed")
|
||||
require.NoError(t, gitWork.Commit(ctx, git.CommitRequest{
|
||||
Message: "commit from test",
|
||||
}), "could not commit changes")
|
||||
|
||||
t.Logf("Pushing to origin: %s", branchWithAssignees)
|
||||
require.NoError(t,
|
||||
gitWork.Push(ctx, git.PushOptions{
|
||||
Remote: "origin",
|
||||
Refspec: git.Refspec(branchWithAssignees),
|
||||
}), "error pushing branch")
|
||||
|
||||
t.Cleanup(func() {
|
||||
ctx := context.WithoutCancel(t.Context())
|
||||
t.Logf("Deleting remote branches: %s, %s", branchNoAssignees, branchWithAssignees)
|
||||
assert.NoError(t,
|
||||
gitWork.Push(ctx, git.PushOptions{
|
||||
Remote: "origin",
|
||||
Refspec: git.Refspec(":" + branchNoAssignees),
|
||||
}), "error deleting branch")
|
||||
assert.NoError(t,
|
||||
gitWork.Push(ctx, git.PushOptions{
|
||||
Remote: "origin",
|
||||
Refspec: git.Refspec(":" + branchWithAssignees),
|
||||
}), "error deleting branch")
|
||||
})
|
||||
}
|
||||
|
||||
ctx := t.Context()
|
||||
rec := newRecorder(t, t.Name())
|
||||
glc := newGitLabClient(rec.GetDefaultClient())
|
||||
repo, err := gitlab.NewRepository(
|
||||
ctx, new(gitlab.Forge), "abg", "test-repo", silogtest.New(t), glc, &gitlab.RepositoryOptions{RepositoryID: _testRepoID},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
changeNoAssignees, err := repo.SubmitChange(ctx, forge.SubmitChangeRequest{
|
||||
Subject: branchNoAssignees,
|
||||
Body: "Test MR without assignees",
|
||||
Base: "main",
|
||||
Head: branchNoAssignees,
|
||||
})
|
||||
require.NoError(t, err, "error creating MR without assignees")
|
||||
|
||||
changeWithAssignees, err := repo.SubmitChange(ctx, forge.SubmitChangeRequest{
|
||||
Subject: branchWithAssignees,
|
||||
Body: "Test MR with assignees",
|
||||
Base: "main",
|
||||
Head: branchWithAssignees,
|
||||
Assignees: []string{"abg"},
|
||||
})
|
||||
require.NoError(t, err, "error creating MR with assignees")
|
||||
|
||||
t.Run("NoAssignees", func(t *testing.T) {
|
||||
change, err := repo.FindChangeByID(t.Context(), changeNoAssignees.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, change.Assignees)
|
||||
|
||||
t.Run("AddAssignees", func(t *testing.T) {
|
||||
require.NoError(t,
|
||||
repo.EditChange(t.Context(), changeNoAssignees.ID, forge.EditChangeOptions{
|
||||
Assignees: []string{"abg"},
|
||||
}), "could not add assignees to MR")
|
||||
|
||||
change, err := repo.FindChangeByID(t.Context(), changeNoAssignees.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"abg"}, change.Assignees)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("WithAssignees", func(t *testing.T) {
|
||||
change, err := repo.FindChangeByID(t.Context(), changeWithAssignees.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"abg"}, change.Assignees)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegration_Repository_SubmitChange_removeSourceBranch(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
branchFixture := fixturetest.New(_fixtures, "branch", func() string {
|
||||
|
||||
@@ -31,6 +31,7 @@ func (r *Repository) SubmitChange(ctx context.Context, req forge.SubmitChangeReq
|
||||
if len(req.Labels) > 0 {
|
||||
input.Labels = (*gitlab.LabelOptions)(&req.Labels)
|
||||
}
|
||||
|
||||
if len(req.Reviewers) > 0 {
|
||||
reviewerIDs, err := r.resolveReviewerIDs(ctx, req.Reviewers)
|
||||
if err != nil {
|
||||
@@ -38,6 +39,15 @@ func (r *Repository) SubmitChange(ctx context.Context, req forge.SubmitChangeReq
|
||||
}
|
||||
input.ReviewerIDs = &reviewerIDs
|
||||
}
|
||||
|
||||
if len(req.Assignees) > 0 {
|
||||
assigneeIDs, err := r.assigneeIDs(ctx, req.Assignees)
|
||||
if err != nil {
|
||||
return forge.SubmitChangeResult{}, fmt.Errorf("resolve assignees: %w", err)
|
||||
}
|
||||
input.AssigneeIDs = &assigneeIDs
|
||||
}
|
||||
|
||||
request, _, err := r.client.MergeRequests.CreateMergeRequest(
|
||||
r.repoID, input,
|
||||
gitlab.WithContext(ctx),
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
"9cyQTb6M"
|
||||
+1
@@ -0,0 +1 @@
|
||||
"o9NxTgJQ"
|
||||
+1
-1
@@ -1 +1 @@
|
||||
"gz2WHyJk"
|
||||
"wmgjGcz8"
|
||||
+1
-1
@@ -1 +1 @@
|
||||
"crSh9OsF"
|
||||
"OuNox4Bt"
|
||||
internal/forge/gitlab/testdata/fixtures/TestIntegration_Repository_FindChangeItem_WithAssignees.yaml
Vendored
+307
File diff suppressed because one or more lines are too long
internal/forge/gitlab/testdata/fixtures/TestIntegration_Repository_FindChangeItem_WithReviewers.yaml
Vendored
+71
-46
File diff suppressed because one or more lines are too long
@@ -98,6 +98,9 @@ type shamChange struct {
|
||||
// RequestedReviewers are the usernames of users
|
||||
// from whom reviews have been requested.
|
||||
RequestedReviewers []string
|
||||
|
||||
// Assignees are users assigned to the change.
|
||||
Assignees []string
|
||||
}
|
||||
|
||||
// Change is a change proposal against a repository.
|
||||
@@ -142,6 +145,9 @@ type Change struct {
|
||||
// RequestedReviewers are the usernames of users
|
||||
// from whom reviews have been requested.
|
||||
RequestedReviewers []string `json:"requested_reviewers,omitempty"`
|
||||
|
||||
// Assignees are users assigned to the change.
|
||||
Assignees []string `json:"assignees,omitempty"`
|
||||
}
|
||||
|
||||
// toChange converts an internal shamChange
|
||||
@@ -161,6 +167,9 @@ func (sh *ShamHub) toChange(c shamChange) (*Change, error) {
|
||||
requestedReviewers := slices.Clone(c.RequestedReviewers)
|
||||
slices.Sort(requestedReviewers)
|
||||
|
||||
assignees := slices.Clone(c.Assignees)
|
||||
slices.Sort(assignees)
|
||||
|
||||
change := &Change{
|
||||
Number: c.Number,
|
||||
URL: sh.changeURL(c.Base.Owner, c.Base.Repo, c.Number),
|
||||
@@ -171,6 +180,7 @@ func (sh *ShamHub) toChange(c shamChange) (*Change, error) {
|
||||
Head: head,
|
||||
Labels: c.Labels,
|
||||
RequestedReviewers: requestedReviewers,
|
||||
Assignees: assignees,
|
||||
}
|
||||
switch c.State {
|
||||
case shamChangeOpen:
|
||||
|
||||
@@ -18,6 +18,7 @@ type editChangeRequest struct {
|
||||
Draft *bool `json:"draft,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
Reviewers []string `json:"reviewers,omitempty"`
|
||||
Assignees []string `json:"assignees,omitempty"`
|
||||
}
|
||||
|
||||
type editChangeResponse struct{}
|
||||
@@ -79,6 +80,16 @@ func (sh *ShamHub) handleEditChange(_ context.Context, req *editChangeRequest) (
|
||||
sh.changes[changeIdx].RequestedReviewers = reviewers
|
||||
}
|
||||
|
||||
if len(req.Assignees) > 0 {
|
||||
assignees := sh.changes[changeIdx].Assignees
|
||||
for _, assignee := range req.Assignees {
|
||||
if !slices.Contains(assignees, assignee) {
|
||||
assignees = append(assignees, assignee)
|
||||
}
|
||||
}
|
||||
sh.changes[changeIdx].Assignees = assignees
|
||||
}
|
||||
|
||||
return &editChangeResponse{}, nil // empty for now
|
||||
}
|
||||
|
||||
@@ -92,6 +103,7 @@ func (r *forgeRepository) EditChange(ctx context.Context, fid forge.ChangeID, op
|
||||
}
|
||||
req.Labels = opts.Labels
|
||||
req.Reviewers = opts.Reviewers
|
||||
req.Assignees = opts.Assignees
|
||||
|
||||
id := fid.(ChangeID)
|
||||
u := r.apiURL.JoinPath(r.owner, r.repo, "change", strconv.Itoa(int(id)))
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"go.abhg.dev/gs/internal/forge"
|
||||
@@ -21,6 +22,7 @@ type submitChangeRequest struct {
|
||||
Draft bool `json:"draft,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
Reviewers []string `json:"reviewers,omitempty"`
|
||||
Assignees []string `json:"assignees,omitempty"`
|
||||
}
|
||||
|
||||
type submitChangeResponse struct {
|
||||
@@ -56,18 +58,22 @@ func (sh *ShamHub) handleSubmitChange(ctx context.Context, req *submitChangeRequ
|
||||
|
||||
// Validate that all requested reviewers are registered users.
|
||||
for _, reviewer := range req.Reviewers {
|
||||
found := false
|
||||
for _, u := range sh.users {
|
||||
if u.Username == reviewer {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if !slices.ContainsFunc(sh.users, func(u shamUser) bool {
|
||||
return u.Username == reviewer
|
||||
}) {
|
||||
return nil, badRequestErrorf("reviewer %q is not a registered user", reviewer)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that all assignees are registered users.
|
||||
for _, assignee := range req.Assignees {
|
||||
if !slices.ContainsFunc(sh.users, func(u shamUser) bool {
|
||||
return u.Username == assignee
|
||||
}) {
|
||||
return nil, badRequestErrorf("assignee %q is not a registered user", assignee)
|
||||
}
|
||||
}
|
||||
|
||||
change := shamChange{
|
||||
// We'll just use a global counter for the change number for now.
|
||||
// We can scope it by owner/repo if needed.
|
||||
@@ -79,6 +85,7 @@ func (sh *ShamHub) handleSubmitChange(ctx context.Context, req *submitChangeRequ
|
||||
Head: &shamBranch{Owner: headOwner, Repo: headRepo, Name: req.Head},
|
||||
Labels: req.Labels,
|
||||
RequestedReviewers: req.Reviewers,
|
||||
Assignees: req.Assignees,
|
||||
}
|
||||
sh.changes = append(sh.changes, change)
|
||||
|
||||
@@ -99,6 +106,7 @@ func (r *forgeRepository) SubmitChange(ctx context.Context, req forge.SubmitChan
|
||||
Draft: req.Draft,
|
||||
Labels: req.Labels,
|
||||
Reviewers: req.Reviewers,
|
||||
Assignees: req.Assignees,
|
||||
}
|
||||
|
||||
// For now, fork functionality is handled at the ShamHub server level
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"go.abhg.dev/gs/internal/browser"
|
||||
"go.abhg.dev/gs/internal/forge"
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/iterutil"
|
||||
"go.abhg.dev/gs/internal/must"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
@@ -151,7 +152,6 @@ type Options struct {
|
||||
DraftDefault bool `config:"submit.draft" help:"Default value for --draft when creating change requests." hidden:"" default:"false"`
|
||||
|
||||
// TODO: Other creation options e.g.:
|
||||
// - assignees
|
||||
// - milestone
|
||||
// - reviewers
|
||||
|
||||
@@ -161,6 +161,9 @@ type Options struct {
|
||||
Reviewers []string `short:"r" name:"reviewer" help:"Add reviewers to the change request. Pass multiple times or separate with commas." released:"unreleased"`
|
||||
ConfiguredReviewers []string `name:"configured-reviewers" help:"Default reviewers to add to change requests." hidden:"" config:"submit.reviewers" released:"unreleased"` // merged with Reviewers
|
||||
|
||||
Assignees []string `short:"a" name:"assign" placeholder:"ASSIGNEE" help:"Assign the change request to these users. Pass multiple times or separate with commas." released:"unreleased"`
|
||||
ConfiguredAssignees []string `name:"configured-assignees" help:"Default assignees to add to change requests." hidden:"" config:"submit.assignees" released:"unreleased"` // merged with Assignees
|
||||
|
||||
// ListTemplatesTimeout controls the timeout for listing CR templates.
|
||||
ListTemplatesTimeout time.Duration `hidden:"" config:"submit.listTemplatesTimeout" help:"Timeout for listing CR templates" default:"1s"`
|
||||
|
||||
@@ -170,6 +173,16 @@ type Options struct {
|
||||
Template string `hidden:"" config:"submit.template" help:"Default template to use when multiple templates are available"`
|
||||
}
|
||||
|
||||
func mergeConfiguredValues(values []string, configured []string) []string {
|
||||
return slices.Collect(iterutil.Uniq(values, configured))
|
||||
}
|
||||
|
||||
func mergeConfiguredOptions(opts *Options) {
|
||||
opts.Labels = mergeConfiguredValues(opts.Labels, opts.ConfiguredLabels)
|
||||
opts.Reviewers = mergeConfiguredValues(opts.Reviewers, opts.ConfiguredReviewers)
|
||||
opts.Assignees = mergeConfiguredValues(opts.Assignees, opts.ConfiguredAssignees)
|
||||
}
|
||||
|
||||
// NavCommentSync specifies the scope of navigation comment updates.
|
||||
type NavCommentSync int
|
||||
|
||||
@@ -273,32 +286,7 @@ type BatchRequest struct {
|
||||
// creating or updating change requests as needed.
|
||||
func (h *Handler) SubmitBatch(ctx context.Context, req *BatchRequest) error {
|
||||
opts := cmp.Or(req.Options, &Options{})
|
||||
|
||||
if len(opts.ConfiguredLabels) > 0 {
|
||||
seen := make(map[string]struct{}, len(opts.Labels))
|
||||
for _, label := range opts.Labels {
|
||||
seen[label] = struct{}{}
|
||||
}
|
||||
|
||||
for _, label := range opts.ConfiguredLabels {
|
||||
if _, ok := seen[label]; !ok {
|
||||
opts.Labels = append(opts.Labels, label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts.ConfiguredReviewers) > 0 {
|
||||
seen := make(map[string]struct{}, len(opts.Reviewers))
|
||||
for _, reviewer := range opts.Reviewers {
|
||||
seen[reviewer] = struct{}{}
|
||||
}
|
||||
|
||||
for _, reviewer := range opts.ConfiguredReviewers {
|
||||
if _, ok := seen[reviewer]; !ok {
|
||||
opts.Reviewers = append(opts.Reviewers, reviewer)
|
||||
}
|
||||
}
|
||||
}
|
||||
mergeConfiguredOptions(opts)
|
||||
|
||||
batchOpts := cmp.Or(req.BatchOptions, &BatchOptions{})
|
||||
if batchOpts.UpdateOnlyDefault && opts.UpdateOnly == nil {
|
||||
@@ -357,19 +345,7 @@ type Request struct {
|
||||
// creating or updating a change request as needed.
|
||||
func (h *Handler) Submit(ctx context.Context, req *Request) error {
|
||||
opts := cmp.Or(req.Options, &Options{})
|
||||
|
||||
if len(opts.ConfiguredReviewers) > 0 {
|
||||
seen := make(map[string]struct{}, len(opts.Reviewers))
|
||||
for _, reviewer := range opts.Reviewers {
|
||||
seen[reviewer] = struct{}{}
|
||||
}
|
||||
|
||||
for _, reviewer := range opts.ConfiguredReviewers {
|
||||
if _, ok := seen[reviewer]; !ok {
|
||||
opts.Reviewers = append(opts.Reviewers, reviewer)
|
||||
}
|
||||
}
|
||||
}
|
||||
mergeConfiguredOptions(opts)
|
||||
status, err := h.submitBranch(
|
||||
ctx,
|
||||
req.Branch,
|
||||
@@ -796,6 +772,24 @@ func (h *Handler) submitBranch(
|
||||
updates = append(updates, "set draft to "+strconv.FormatBool(*opts.Draft))
|
||||
}
|
||||
|
||||
if len(opts.Assignees) > 0 {
|
||||
existingAssigneeSet := make(map[string]struct{}, len(pull.Assignees))
|
||||
for _, assignee := range pull.Assignees {
|
||||
existingAssigneeSet[assignee] = struct{}{}
|
||||
}
|
||||
|
||||
var assigneesToAdd []string
|
||||
for _, assignee := range opts.Assignees {
|
||||
if _, exists := existingAssigneeSet[assignee]; !exists {
|
||||
assigneesToAdd = append(assigneesToAdd, assignee)
|
||||
}
|
||||
}
|
||||
if len(assigneesToAdd) > 0 {
|
||||
sort.Strings(assigneesToAdd)
|
||||
updates = append(updates, "add assignees: "+strings.Join(assigneesToAdd, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
// Check for labels that would be added.
|
||||
if len(opts.Labels) > 0 {
|
||||
existingLabelSet := make(map[string]struct{}, len(pull.Labels))
|
||||
@@ -875,6 +869,7 @@ func (h *Handler) submitBranch(
|
||||
Draft: opts.Draft,
|
||||
Labels: opts.Labels,
|
||||
Reviewers: opts.Reviewers,
|
||||
Assignees: opts.Assignees,
|
||||
}
|
||||
|
||||
// remoteRepo is guaranteed to be available at this point.
|
||||
@@ -1078,6 +1073,7 @@ func (h *Handler) prepareBranch(
|
||||
log: h.Log,
|
||||
labels: opts.Labels,
|
||||
reviewers: opts.Reviewers,
|
||||
assignees: opts.Assignees,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1118,6 +1114,7 @@ type preparedBranch struct {
|
||||
draft bool
|
||||
labels []string
|
||||
reviewers []string
|
||||
assignees []string
|
||||
|
||||
remoteRepo forge.Repository
|
||||
store Store
|
||||
@@ -1133,6 +1130,7 @@ func (b *preparedBranch) Publish(ctx context.Context) (forge.ChangeID, string, e
|
||||
Draft: b.draft,
|
||||
Labels: b.labels,
|
||||
Reviewers: b.reviewers,
|
||||
Assignees: b.assignees,
|
||||
})
|
||||
if err != nil {
|
||||
// If the branch could not be submitted because the base branch
|
||||
|
||||
@@ -15,3 +15,23 @@ func Enumerate[T any](seq iter.Seq[T]) iter.Seq2[int, T] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Uniq returns an iterator that yields unique values
|
||||
// from all provided slices in order,
|
||||
// skipping duplicates.
|
||||
func Uniq[T comparable](slices ...[]T) iter.Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
seen := make(map[T]struct{})
|
||||
for _, slice := range slices {
|
||||
for _, item := range slice {
|
||||
if _, ok := seen[item]; ok {
|
||||
continue
|
||||
}
|
||||
seen[item] = struct{}{}
|
||||
if !yield(item) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,74 @@ func TestEnumerate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestUniq(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
give [][]string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "SingleSliceNoDuplicates",
|
||||
give: [][]string{{"foo", "bar", "baz"}},
|
||||
want: []string{"foo", "bar", "baz"},
|
||||
},
|
||||
{
|
||||
name: "SingleSliceWithDuplicates",
|
||||
give: [][]string{{"foo", "bar", "foo", "baz", "bar"}},
|
||||
want: []string{"foo", "bar", "baz"},
|
||||
},
|
||||
{
|
||||
name: "MultipleSlicesNoDuplicates",
|
||||
give: [][]string{{"a", "b"}, {"c", "d"}},
|
||||
want: []string{"a", "b", "c", "d"},
|
||||
},
|
||||
{
|
||||
name: "MultipleSlicesWithDuplicatesAcrossSlices",
|
||||
give: [][]string{{"a", "b", "c"}, {"b", "c", "d"}, {"c", "d", "e"}},
|
||||
want: []string{"a", "b", "c", "d", "e"},
|
||||
},
|
||||
{
|
||||
name: "MultipleSlicesWithDuplicatesWithinAndAcross",
|
||||
give: [][]string{{"a", "b", "b", "c"}, {"b", "c", "d", "d"}, {"c", "d", "e"}},
|
||||
want: []string{"a", "b", "c", "d", "e"},
|
||||
},
|
||||
{
|
||||
name: "EmptySlicesMixed",
|
||||
give: [][]string{{"a"}, {}, {"b", "a"}, {}},
|
||||
want: []string{"a", "b"},
|
||||
},
|
||||
{
|
||||
name: "PreserveOrder",
|
||||
give: [][]string{{"z", "a", "m"}, {"a", "b", "z"}},
|
||||
want: []string{"z", "a", "m", "b"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := slices.Collect(Uniq(tt.give...))
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("EmptySlices", func(t *testing.T) {
|
||||
got := slices.Collect(Uniq[string]())
|
||||
assert.Empty(t, got)
|
||||
})
|
||||
|
||||
t.Run("EarlyTermination", func(t *testing.T) {
|
||||
var got []int
|
||||
for val := range Uniq([]int{1, 2, 3, 4, 5}, []int{6, 7, 8}) {
|
||||
got = append(got, val)
|
||||
if val == 3 {
|
||||
break
|
||||
}
|
||||
}
|
||||
want := []int{1, 2, 3}
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
|
||||
type indexed[T any] struct {
|
||||
Index int
|
||||
Value T
|
||||
|
||||
Vendored
+3
@@ -46,6 +46,8 @@ Flags:
|
||||
times or separate with commas.
|
||||
-r, --reviewer=REVIEWER,... Add reviewers to the change request. Pass
|
||||
multiple times or separate with commas.
|
||||
-a, --assign=ASSIGNEE,... Assign the change request to these users.
|
||||
Pass multiple times or separate with commas.
|
||||
--no-web Alias for --web=false.
|
||||
--title=TITLE Title of the change request
|
||||
--body=BODY Body of the change request
|
||||
@@ -59,6 +61,7 @@ Global Flags:
|
||||
--[no-]prompt Whether to prompt for missing information
|
||||
|
||||
Configuration (🔧):
|
||||
spice.submit.assignees Default assignees to add to change requests.
|
||||
spice.submit.draft Default value for --draft when creating change
|
||||
requests.
|
||||
spice.submit.label Default labels to add to change requests.
|
||||
|
||||
Vendored
+3
@@ -47,6 +47,8 @@ Flags:
|
||||
times or separate with commas.
|
||||
-r, --reviewer=REVIEWER,... Add reviewers to the change request. Pass
|
||||
multiple times or separate with commas.
|
||||
-a, --assign=ASSIGNEE,... Assign the change request to these users.
|
||||
Pass multiple times or separate with commas.
|
||||
--no-web Alias for --web=false.
|
||||
--branch=NAME Branch to start at
|
||||
|
||||
@@ -58,6 +60,7 @@ Global Flags:
|
||||
--[no-]prompt Whether to prompt for missing information
|
||||
|
||||
Configuration (🔧):
|
||||
spice.submit.assignees Default assignees to add to change requests.
|
||||
spice.submit.draft Default value for --draft when creating change
|
||||
requests.
|
||||
spice.submit.label Default labels to add to change requests.
|
||||
|
||||
Vendored
+3
@@ -46,6 +46,8 @@ Flags:
|
||||
times or separate with commas.
|
||||
-r, --reviewer=REVIEWER,... Add reviewers to the change request. Pass
|
||||
multiple times or separate with commas.
|
||||
-a, --assign=ASSIGNEE,... Assign the change request to these users.
|
||||
Pass multiple times or separate with commas.
|
||||
--no-web Alias for --web=false.
|
||||
|
||||
Global Flags:
|
||||
@@ -56,6 +58,7 @@ Global Flags:
|
||||
--[no-]prompt Whether to prompt for missing information
|
||||
|
||||
Configuration (🔧):
|
||||
spice.submit.assignees Default assignees to add to change requests.
|
||||
spice.submit.draft Default value for --draft when creating change
|
||||
requests.
|
||||
spice.submit.label Default labels to add to change requests.
|
||||
|
||||
Vendored
+3
@@ -49,6 +49,8 @@ Flags:
|
||||
times or separate with commas.
|
||||
-r, --reviewer=REVIEWER,... Add reviewers to the change request. Pass
|
||||
multiple times or separate with commas.
|
||||
-a, --assign=ASSIGNEE,... Assign the change request to these users.
|
||||
Pass multiple times or separate with commas.
|
||||
--no-web Alias for --web=false.
|
||||
--branch=NAME Branch to start at
|
||||
|
||||
@@ -60,6 +62,7 @@ Global Flags:
|
||||
--[no-]prompt Whether to prompt for missing information
|
||||
|
||||
Configuration (🔧):
|
||||
spice.submit.assignees Default assignees to add to change requests.
|
||||
spice.submit.draft Default value for --draft when creating change
|
||||
requests.
|
||||
spice.submit.label Default labels to add to change requests.
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# Submit a single branch with assignees.
|
||||
|
||||
as 'Test <test@example.com>'
|
||||
at '2024-04-05T16:40:32Z'
|
||||
|
||||
# setup
|
||||
cd repo
|
||||
git init
|
||||
git commit --allow-empty -m 'Initial commit'
|
||||
|
||||
# set up a fake GitHub remote
|
||||
shamhub init
|
||||
shamhub new origin alice/example.git
|
||||
shamhub register alice
|
||||
shamhub register bob
|
||||
shamhub register charlie
|
||||
git push origin main
|
||||
|
||||
# create a branch
|
||||
git add feature.txt
|
||||
gs branch create feature -m 'Add feature'
|
||||
|
||||
env SHAMHUB_USERNAME=alice
|
||||
gs auth login
|
||||
|
||||
git config spice.submit.assignees bob
|
||||
|
||||
# Submit the branch with an additional assignee.
|
||||
gs branch submit --fill --assign charlie
|
||||
shamhub dump changes
|
||||
cmpenvJSON stdout $WORK/golden/submitted.json
|
||||
|
||||
# Update the branch with a new assignee.
|
||||
gs branch submit --assign dave
|
||||
shamhub dump changes
|
||||
cmpenvJSON stdout $WORK/golden/updated.json
|
||||
|
||||
-- repo/feature.txt --
|
||||
This is a feature
|
||||
-- golden/submitted.json --
|
||||
[
|
||||
{
|
||||
"number": 1,
|
||||
"state": "open",
|
||||
"title": "Add feature",
|
||||
"body": "",
|
||||
"html_url": "$SHAMHUB_URL/alice/example/change/1",
|
||||
"head": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "feature",
|
||||
"sha": "79b9cc53bc9d9c20db08dd3f9b3d969a06c9d049"
|
||||
},
|
||||
"base": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "main",
|
||||
"sha": "ece8ed7bb81d74cb6787309fa41b7deb2e0558a3"
|
||||
},
|
||||
"assignees": ["bob", "charlie"]
|
||||
}
|
||||
]
|
||||
|
||||
-- golden/updated.json --
|
||||
[
|
||||
{
|
||||
"number": 1,
|
||||
"state": "open",
|
||||
"title": "Add feature",
|
||||
"body": "",
|
||||
"html_url": "$SHAMHUB_URL/alice/example/change/1",
|
||||
"head": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "feature",
|
||||
"sha": "79b9cc53bc9d9c20db08dd3f9b3d969a06c9d049"
|
||||
},
|
||||
"base": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "main",
|
||||
"sha": "ece8ed7bb81d74cb6787309fa41b7deb2e0558a3"
|
||||
},
|
||||
"assignees": ["bob", "charlie", "dave"]
|
||||
}
|
||||
]
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
# submit a stack of PRs with 'stack submit'.
|
||||
|
||||
as 'Test <test@example.com>'
|
||||
at '2024-04-05T16:40:32Z'
|
||||
|
||||
# setup
|
||||
cd repo
|
||||
git init
|
||||
git commit --allow-empty -m 'Initial commit'
|
||||
|
||||
# set up a fake GitHub remote
|
||||
shamhub init
|
||||
shamhub new origin alice/example.git
|
||||
shamhub register alice
|
||||
shamhub register alex
|
||||
shamhub register beatrice
|
||||
shamhub register cameron
|
||||
shamhub register dorian
|
||||
shamhub register spice
|
||||
git push origin main
|
||||
|
||||
# create a stack:
|
||||
# main -> feature1 -> feature2 -> feature3
|
||||
git add feature1.txt
|
||||
gs branch create feature1 -m 'Add feature 1'
|
||||
git add feature2.txt
|
||||
gs branch create feature2 -m 'Add feature 2'
|
||||
git add feature3.txt
|
||||
gs branch create feature3 -m 'Add feature 3'
|
||||
|
||||
env SHAMHUB_USERNAME=alice
|
||||
gs auth login
|
||||
|
||||
git config spice.submit.assignees spice
|
||||
|
||||
# submit the entire stack from the middle.
|
||||
git checkout feature1
|
||||
gs stack submit --fill --assign alex --assign beatrice,cameron
|
||||
cmpenv stderr $WORK/golden/submit-log.txt
|
||||
|
||||
shamhub dump changes
|
||||
cmpenvJSON stdout $WORK/golden/start.json
|
||||
|
||||
# Merge the bottom PR, sync, restack, and submit with new assignees.
|
||||
shamhub merge alice/example 1
|
||||
gs rs
|
||||
stderr '#1 was merged'
|
||||
gs sr # stack restack
|
||||
gs ss --assign dorian # stack submit
|
||||
! stderr 'Updated #1'
|
||||
stderr 'Updated #2'
|
||||
stderr 'Updated #3'
|
||||
|
||||
shamhub dump changes
|
||||
cmpenvJSON stdout $WORK/golden/updated.json
|
||||
|
||||
-- repo/feature1.txt --
|
||||
This is feature 1
|
||||
-- repo/feature2.txt --
|
||||
This is feature 2
|
||||
-- repo/feature3.txt --
|
||||
This is feature 3
|
||||
|
||||
-- golden/submit-log.txt --
|
||||
INF Created #1: $SHAMHUB_URL/alice/example/change/1
|
||||
INF Created #2: $SHAMHUB_URL/alice/example/change/2
|
||||
INF Created #3: $SHAMHUB_URL/alice/example/change/3
|
||||
-- golden/start.json --
|
||||
[
|
||||
{
|
||||
"number": 1,
|
||||
"state": "open",
|
||||
"title": "Add feature 1",
|
||||
"body": "",
|
||||
"html_url": "$SHAMHUB_URL/alice/example/change/1",
|
||||
"head": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "feature1",
|
||||
"sha": "8526d1a7195abb635f28bc93155b9155b76f3881"
|
||||
},
|
||||
"base": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "main",
|
||||
"sha": "ece8ed7bb81d74cb6787309fa41b7deb2e0558a3"
|
||||
},
|
||||
"assignees": ["alex", "beatrice", "cameron", "spice"]
|
||||
},
|
||||
{
|
||||
"number": 2,
|
||||
"state": "open",
|
||||
"title": "Add feature 2",
|
||||
"body": "",
|
||||
"html_url": "$SHAMHUB_URL/alice/example/change/2",
|
||||
"head": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "feature2",
|
||||
"sha": "9806160f3b27dbff81e496260d26fc32f5ee5cf0"
|
||||
},
|
||||
"base": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "feature1",
|
||||
"sha": "8526d1a7195abb635f28bc93155b9155b76f3881"
|
||||
},
|
||||
"assignees": ["alex", "beatrice", "cameron", "spice"]
|
||||
},
|
||||
{
|
||||
"number": 3,
|
||||
"state": "open",
|
||||
"title": "Add feature 3",
|
||||
"body": "",
|
||||
"html_url": "$SHAMHUB_URL/alice/example/change/3",
|
||||
"head": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "feature3",
|
||||
"sha": "63b2d337c8172c9f79aec9c760efc95e3c0c8472"
|
||||
},
|
||||
"base": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "feature2",
|
||||
"sha": "9806160f3b27dbff81e496260d26fc32f5ee5cf0"
|
||||
},
|
||||
"assignees": ["alex", "beatrice", "cameron", "spice"]
|
||||
}
|
||||
]
|
||||
|
||||
-- golden/updated.json --
|
||||
[
|
||||
{
|
||||
"number": 1,
|
||||
"state": "closed",
|
||||
"merged": true,
|
||||
"title": "Add feature 1",
|
||||
"body": "",
|
||||
"html_url": "$SHAMHUB_URL/alice/example/change/1",
|
||||
"head": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "feature1",
|
||||
"sha": "8526d1a7195abb635f28bc93155b9155b76f3881"
|
||||
},
|
||||
"base": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "main",
|
||||
"sha": "59d3064874a8d0a003755556a982d1fe8adb59dc"
|
||||
},
|
||||
"assignees": ["alex", "beatrice", "cameron", "spice"]
|
||||
},
|
||||
{
|
||||
"number": 2,
|
||||
"state": "open",
|
||||
"title": "Add feature 2",
|
||||
"body": "",
|
||||
"html_url": "$SHAMHUB_URL/alice/example/change/2",
|
||||
"head": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "feature2",
|
||||
"sha": "18e5a5208175cd409480c2ab89588877726c1d57"
|
||||
},
|
||||
"base": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "main",
|
||||
"sha": "59d3064874a8d0a003755556a982d1fe8adb59dc"
|
||||
},
|
||||
"assignees": ["alex", "beatrice", "cameron", "dorian", "spice"]
|
||||
},
|
||||
{
|
||||
"number": 3,
|
||||
"state": "open",
|
||||
"title": "Add feature 3",
|
||||
"body": "",
|
||||
"html_url": "$SHAMHUB_URL/alice/example/change/3",
|
||||
"head": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "feature3",
|
||||
"sha": "aa7889fb8c0037d05573a0d27cacfcc78c5f1ba4"
|
||||
},
|
||||
"base": {
|
||||
"repository": {
|
||||
"owner": "alice",
|
||||
"name": "example"
|
||||
},
|
||||
"ref": "feature2",
|
||||
"sha": "18e5a5208175cd409480c2ab89588877726c1d57"
|
||||
},
|
||||
"assignees": ["alex", "beatrice", "cameron", "dorian", "spice"]
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user