Merge pull request #4886 from g14a/feature/target-repo-fork-name

Feature/target repo fork name
This commit is contained in:
Nate Smith 2022-01-25 15:57:16 -06:00 committed by GitHub
commit a564909f03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 140 additions and 68 deletions

View file

@ -326,6 +326,7 @@ func (c Client) REST(hostname string, method string, p string, body io.Reader, d
if err != nil {
return err
}
err = json.Unmarshal(b, &data)
if err != nil {
return err

View file

@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/cli/cli/v2/internal/ghinstance"
"io"
"net/http"
"sort"
@ -524,6 +525,37 @@ func ForkRepo(client *Client, repo ghrepo.Interface, org string) (*Repository, e
}, nil
}
// RenameRepo renames the repository on GitHub and returns the renamed repository
func RenameRepo(client *Client, repo ghrepo.Interface, newRepoName string) (*Repository, error) {
input := map[string]string{"name": newRepoName}
body := &bytes.Buffer{}
enc := json.NewEncoder(body)
if err := enc.Encode(input); err != nil {
return nil, err
}
path := fmt.Sprintf("%srepos/%s",
ghinstance.RESTPrefix(repo.RepoHost()),
ghrepo.FullName(repo))
result := repositoryV3{}
err := client.REST(repo.RepoHost(), "PATCH", path, body, &result)
if err != nil {
return nil, err
}
return &Repository{
ID: result.NodeID,
Name: result.Name,
CreatedAt: result.CreatedAt,
Owner: RepositoryOwner{
Login: result.Owner.Login,
},
ViewerPermission: "WRITE",
hostname: repo.RepoHost(),
}, nil
}
func LastCommit(client *Client, repo ghrepo.Interface) (*Commit, error) {
var responseData struct {
Repository struct {

View file

@ -39,6 +39,7 @@ type ForkOptions struct {
PromptRemote bool
RemoteName string
Organization string
ForkName string
Rename bool
}
@ -115,6 +116,7 @@ Additional 'git clone' flags can be passed in by listing them after '--'.`,
cmd.Flags().BoolVar(&opts.Remote, "remote", false, "Add remote for fork {true|false}")
cmd.Flags().StringVar(&opts.RemoteName, "remote-name", defaultRemoteName, "Specify a name for a fork's new remote.")
cmd.Flags().StringVar(&opts.Organization, "org", "", "Create the fork in an organization")
cmd.Flags().StringVar(&opts.ForkName, "fork-name", "", "Specify a name for the forked repo")
return cmd
}
@ -201,6 +203,17 @@ func forkRun(opts *ForkOptions) error {
}
}
// Rename the forked repo if ForkName is specified in opts.
if opts.ForkName != "" {
forkedRepo, err = api.RenameRepo(apiClient, forkedRepo, opts.ForkName)
if err != nil {
return fmt.Errorf("could not rename fork: %w", err)
}
if connectedToTerminal {
fmt.Fprintf(stderr, "%s Renamed fork to %s\n", cs.SuccessIconWithColor(cs.Green), cs.Bold(ghrepo.FullName(forkedRepo)))
}
}
if (inParent && (!opts.Remote && !opts.PromptRemote)) || (!inParent && (!opts.Clone && !opts.PromptClone)) {
return nil
}

View file

@ -132,6 +132,16 @@ func TestNewCmdFork(t *testing.T) {
wantErr: true,
errMsg: "unknown flag: --depth\nSeparate git clone flags with '--'.",
},
{
name: "with fork name",
cli: "--fork-name new-fork",
wants: ForkOptions{
Remote: false,
RemoteName: "origin",
ForkName: "new-fork",
Rename: false,
},
},
}
for _, tt := range tests {
@ -534,6 +544,78 @@ func TestRepoFork(t *testing.T) {
cs.Register(`git -C REPO remote add -f upstream https://github\.com/OWNER/REPO\.git`, 0, "")
},
},
{
name: "non tty repo arg with fork-name",
opts: &ForkOptions{
Repository: "someone/REPO",
Clone: false,
ForkName: "NEW_REPO",
},
tty: false,
httpStubs: func(reg *httpmock.Registry) {
forkResult := `{
"node_id": "123",
"name": "REPO",
"clone_url": "https://github.com/OWNER/REPO.git",
"created_at": "2011-01-26T19:01:12Z",
"owner": {
"login": "OWNER"
}
}`
renameResult := `{
"node_id": "1234",
"name": "NEW_REPO",
"clone_url": "https://github.com/OWNER/NEW_REPO.git",
"created_at": "2012-01-26T19:01:12Z",
"owner": {
"login": "OWNER"
}
}`
reg.Register(
httpmock.REST("POST", "repos/someone/REPO/forks"),
httpmock.StringResponse(forkResult))
reg.Register(
httpmock.REST("PATCH", "repos/OWNER/REPO"),
httpmock.StringResponse(renameResult))
},
wantErrOut: "",
},
{
name: "tty repo arg with fork-name",
opts: &ForkOptions{
Repository: "someone/REPO",
Clone: false,
ForkName: "NEW_REPO",
},
tty: true,
httpStubs: func(reg *httpmock.Registry) {
forkResult := `{
"node_id": "123",
"name": "REPO",
"clone_url": "https://github.com/OWNER/REPO.git",
"created_at": "2011-01-26T19:01:12Z",
"owner": {
"login": "OWNER"
}
}`
renameResult := `{
"node_id": "1234",
"name": "NEW_REPO",
"clone_url": "https://github.com/OWNER/NEW_REPO.git",
"created_at": "2012-01-26T19:01:12Z",
"owner": {
"login": "OWNER"
}
}`
reg.Register(
httpmock.REST("POST", "repos/someone/REPO/forks"),
httpmock.StringResponse(forkResult))
reg.Register(
httpmock.REST("PATCH", "repos/OWNER/REPO"),
httpmock.StringResponse(renameResult))
},
wantErrOut: "✓ Created fork OWNER/REPO\n✓ Renamed fork to OWNER/NEW_REPO\n",
},
}
for _, tt := range tests {

View file

@ -1,61 +0,0 @@
package rename
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghinstance"
"github.com/cli/cli/v2/internal/ghrepo"
)
func apiRename(client *http.Client, repo ghrepo.Interface, newRepoName string) (ghrepo.Interface, error) {
input := map[string]string{"name": newRepoName}
body, err := json.Marshal(input)
if err != nil {
return nil, err
}
path := fmt.Sprintf("%srepos/%s",
ghinstance.RESTPrefix(repo.RepoHost()),
ghrepo.FullName(repo))
request, err := http.NewRequest("PATCH", path, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := client.Do(request)
if err != nil {
return nil, err
}
if resp.StatusCode > 299 {
return nil, api.HandleHTTPError(resp)
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
result := struct {
Name string
Owner struct {
Login string
}
}{}
if err := json.Unmarshal(b, &result); err != nil {
return nil, fmt.Errorf("error unmarshaling response: %w", err)
}
newRepo := ghrepo.NewWithHost(result.Owner.Login, result.Name, repo.RepoHost())
return newRepo, nil
}

View file

@ -2,6 +2,7 @@ package rename
import (
"fmt"
"github.com/cli/cli/v2/api"
"net/http"
"github.com/AlecAivazis/survey/v2"
@ -114,11 +115,15 @@ func renameRun(opts *RenameOptions) error {
}
}
newRepo, err := apiRename(httpClient, currRepo, newRepoName)
apiClient := api.NewClientFromHTTP(httpClient)
newRepo, err := api.RenameRepo(apiClient, currRepo, newRepoName)
if err != nil {
return err
}
renamedRepo := ghrepo.New(newRepo.Owner.Login, newRepo.Name)
cs := opts.IO.ColorScheme()
if opts.IO.IsStdoutTTY() {
fmt.Fprintf(opts.IO.Out, "%s Renamed repository %s\n", cs.SuccessIcon(), ghrepo.FullName(newRepo))
@ -128,7 +133,7 @@ func renameRun(opts *RenameOptions) error {
return nil
}
remote, err := updateRemote(currRepo, newRepo, opts)
remote, err := updateRemote(currRepo, renamedRepo, opts)
if err != nil {
fmt.Fprintf(opts.IO.ErrOut, "%s Warning: unable to update remote %q: %v\n", cs.WarningIcon(), remote.Name, err)
} else if opts.IO.IsStdoutTTY() {

View file

@ -123,7 +123,7 @@ func TestRenameRun(t *testing.T) {
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("PATCH", "repos/OWNER/REPO"),
httpmock.StatusStringResponse(204, `{"name":"NEW_REPO","owner":{"login":"OWNER"}}`))
httpmock.StatusStringResponse(200, `{"name":"NEW_REPO","owner":{"login":"OWNER"}}`))
},
execStubs: func(cs *run.CommandStubber) {
cs.Register(`git remote set-url origin https://github.com/OWNER/NEW_REPO.git`, 0, "")
@ -143,7 +143,7 @@ func TestRenameRun(t *testing.T) {
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("PATCH", "repos/OWNER/REPO"),
httpmock.StatusStringResponse(204, `{"name":"NEW_REPO","owner":{"login":"OWNER"}}`))
httpmock.StatusStringResponse(200, `{"name":"NEW_REPO","owner":{"login":"OWNER"}}`))
},
tty: true,
},
@ -156,7 +156,7 @@ func TestRenameRun(t *testing.T) {
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("PATCH", "repos/OWNER/REPO"),
httpmock.StatusStringResponse(204, `{"name":"NEW_REPO","owner":{"login":"OWNER"}}`))
httpmock.StatusStringResponse(200, `{"name":"NEW_REPO","owner":{"login":"OWNER"}}`))
},
execStubs: func(cs *run.CommandStubber) {
cs.Register(`git remote set-url origin https://github.com/OWNER/NEW_REPO.git`, 0, "")
@ -171,7 +171,7 @@ func TestRenameRun(t *testing.T) {
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("PATCH", "repos/OWNER/REPO"),
httpmock.StatusStringResponse(204, `{"name":"NEW_REPO","owner":{"login":"OWNER"}}`))
httpmock.StatusStringResponse(200, `{"name":"NEW_REPO","owner":{"login":"OWNER"}}`))
},
execStubs: func(cs *run.CommandStubber) {
cs.Register(`git remote set-url origin https://github.com/OWNER/NEW_REPO.git`, 0, "")
@ -192,7 +192,7 @@ func TestRenameRun(t *testing.T) {
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("PATCH", "repos/OWNER/REPO"),
httpmock.StatusStringResponse(204, `{"name":"NEW_REPO","owner":{"login":"OWNER"}}`))
httpmock.StatusStringResponse(200, `{"name":"NEW_REPO","owner":{"login":"OWNER"}}`))
},
execStubs: func(cs *run.CommandStubber) {
cs.Register(`git remote set-url origin https://github.com/OWNER/NEW_REPO.git`, 0, "")