Skip to content

Commit 3cbd5b4

Browse files
mislavg14a
andauthored
Add repo fork --org functionality (cli#3611)
Co-authored-by: Gowtham Munukutla <gowtham.m81197@gmail.com>
1 parent 026b07d commit 3cbd5b4

File tree

4 files changed

+81
-24
lines changed

4 files changed

+81
-24
lines changed

api/queries_repo.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,20 @@ type repositoryV3 struct {
312312
}
313313

314314
// ForkRepo forks the repository on GitHub and returns the new repository
315-
func ForkRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
315+
func ForkRepo(client *Client, repo ghrepo.Interface, org string) (*Repository, error) {
316316
path := fmt.Sprintf("repos/%s/forks", ghrepo.FullName(repo))
317-
body := bytes.NewBufferString(`{}`)
317+
318+
params := map[string]interface{}{}
319+
if org != "" {
320+
params["organization"] = org
321+
}
322+
323+
body := &bytes.Buffer{}
324+
enc := json.NewEncoder(body)
325+
if err := enc.Encode(params); err != nil {
326+
return nil, err
327+
}
328+
318329
result := repositoryV3{}
319330
err := client.REST(repo.RepoHost(), "POST", path, body, &result)
320331
if err != nil {

pkg/cmd/pr/create/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ func handlePush(opts CreateOptions, ctx CreateContext) error {
674674
// one by forking the base repository
675675
if headRepo == nil && ctx.IsPushEnabled {
676676
opts.IO.StartProgressIndicator()
677-
headRepo, err = api.ForkRepo(client, ctx.BaseRepo)
677+
headRepo, err = api.ForkRepo(client, ctx.BaseRepo, "")
678678
opts.IO.StopProgressIndicator()
679679
if err != nil {
680680
return fmt.Errorf("error forking repo: %w", err)

pkg/cmd/repo/fork/fork.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type ForkOptions struct {
3636
PromptClone bool
3737
PromptRemote bool
3838
RemoteName string
39+
Organization string
3940
Rename bool
4041
}
4142

@@ -110,6 +111,7 @@ Additional 'git clone' flags can be passed in by listing them after '--'.`,
110111
cmd.Flags().BoolVar(&opts.Clone, "clone", false, "Clone the fork {true|false}")
111112
cmd.Flags().BoolVar(&opts.Remote, "remote", false, "Add remote for fork {true|false}")
112113
cmd.Flags().StringVar(&opts.RemoteName, "remote-name", "origin", "Specify a name for a fork's new remote.")
114+
cmd.Flags().StringVar(&opts.Organization, "org", "", "Create the fork in an organization")
113115

114116
return cmd
115117
}
@@ -169,7 +171,7 @@ func forkRun(opts *ForkOptions) error {
169171
apiClient := api.NewClientFromHTTP(httpClient)
170172

171173
opts.IO.StartProgressIndicator()
172-
forkedRepo, err := api.ForkRepo(apiClient, repoToFork)
174+
forkedRepo, err := api.ForkRepo(apiClient, repoToFork, opts.Organization)
173175
opts.IO.StopProgressIndicator()
174176
if err != nil {
175177
return fmt.Errorf("failed to fork: %w", err)

pkg/cmd/repo/fork/fork_test.go

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package fork
22

33
import (
44
"bytes"
5+
"io/ioutil"
56
"net/http"
67
"net/url"
78
"regexp"
89
"testing"
910
"time"
1011

12+
"github.com/MakeNowJust/heredoc"
1113
"github.com/cli/cli/context"
1214
"github.com/cli/cli/git"
1315
"github.com/cli/cli/internal/config"
@@ -72,8 +74,9 @@ func TestNewCmdFork(t *testing.T) {
7274
name: "blank nontty",
7375
cli: "",
7476
wants: ForkOptions{
75-
RemoteName: "origin",
76-
Rename: true,
77+
RemoteName: "origin",
78+
Rename: true,
79+
Organization: "",
7780
},
7881
},
7982
{
@@ -85,6 +88,7 @@ func TestNewCmdFork(t *testing.T) {
8588
PromptClone: true,
8689
PromptRemote: true,
8790
Rename: true,
91+
Organization: "",
8892
},
8993
},
9094
{
@@ -104,6 +108,16 @@ func TestNewCmdFork(t *testing.T) {
104108
Rename: true,
105109
},
106110
},
111+
{
112+
name: "to org",
113+
cli: "--org batmanshome",
114+
wants: ForkOptions{
115+
RemoteName: "origin",
116+
Remote: false,
117+
Rename: false,
118+
Organization: "batmanshome",
119+
},
120+
},
107121
}
108122

109123
for _, tt := range tests {
@@ -141,6 +155,7 @@ func TestNewCmdFork(t *testing.T) {
141155
assert.Equal(t, tt.wants.Remote, gotOpts.Remote)
142156
assert.Equal(t, tt.wants.PromptRemote, gotOpts.PromptRemote)
143157
assert.Equal(t, tt.wants.PromptClone, gotOpts.PromptClone)
158+
assert.Equal(t, tt.wants.Organization, gotOpts.Organization)
144159
})
145160
}
146161
}
@@ -289,6 +304,7 @@ func TestRepoFork_in_parent_tty(t *testing.T) {
289304
assert.Equal(t, "✓ Created fork someone/REPO\n✓ Added remote origin\n", output.Stderr())
290305
reg.Verify(t)
291306
}
307+
292308
func TestRepoFork_in_parent_nontty(t *testing.T) {
293309
defer stubSince(2 * time.Second)()
294310
reg := &httpmock.Registry{}
@@ -409,37 +425,65 @@ func TestRepoFork_in_parent(t *testing.T) {
409425

410426
func TestRepoFork_outside(t *testing.T) {
411427
tests := []struct {
412-
name string
413-
args string
428+
name string
429+
args string
430+
postBody string
431+
responseBody string
432+
wantStderr string
414433
}{
415434
{
416-
name: "url arg",
417-
args: "--clone=false http://github.com/OWNER/REPO.git",
435+
name: "url arg",
436+
args: "--clone=false http://github.com/OWNER/REPO.git",
437+
postBody: "{}\n",
438+
responseBody: `{"name":"REPO", "owner":{"login":"monalisa"}}`,
439+
wantStderr: heredoc.Doc(`
440+
✓ Created fork monalisa/REPO
441+
`),
418442
},
419443
{
420-
name: "full name arg",
421-
args: "--clone=false OWNER/REPO",
444+
name: "full name arg",
445+
args: "--clone=false OWNER/REPO",
446+
postBody: "{}\n",
447+
responseBody: `{"name":"REPO", "owner":{"login":"monalisa"}}`,
448+
wantStderr: heredoc.Doc(`
449+
✓ Created fork monalisa/REPO
450+
`),
451+
},
452+
{
453+
name: "fork to org without clone",
454+
args: "--clone=false OWNER/REPO --org batmanshome",
455+
postBody: "{\"organization\":\"batmanshome\"}\n",
456+
responseBody: `{"name":"REPO", "owner":{"login":"BatmansHome"}}`,
457+
wantStderr: heredoc.Doc(`
458+
✓ Created fork BatmansHome/REPO
459+
`),
422460
},
423461
}
424462
for _, tt := range tests {
425463
t.Run(tt.name, func(t *testing.T) {
426464
defer stubSince(2 * time.Second)()
465+
427466
reg := &httpmock.Registry{}
428-
defer reg.StubWithFixturePath(200, "./forkResult.json")()
429-
httpClient := &http.Client{Transport: reg}
467+
reg.Register(
468+
httpmock.REST("POST", "repos/OWNER/REPO/forks"),
469+
func(req *http.Request) (*http.Response, error) {
470+
bb, err := ioutil.ReadAll(req.Body)
471+
if err != nil {
472+
return nil, err
473+
}
474+
assert.Equal(t, tt.postBody, string(bb))
475+
return &http.Response{
476+
Request: req,
477+
StatusCode: 200,
478+
Body: ioutil.NopCloser(bytes.NewBufferString(tt.responseBody)),
479+
}, nil
480+
})
430481

482+
httpClient := &http.Client{Transport: reg}
431483
output, err := runCommand(httpClient, nil, true, tt.args)
432-
if err != nil {
433-
t.Errorf("error running command `repo fork`: %v", err)
434-
}
435-
484+
assert.NoError(t, err)
436485
assert.Equal(t, "", output.String())
437-
438-
r := regexp.MustCompile(`Created fork.*someone/REPO`)
439-
if !r.MatchString(output.Stderr()) {
440-
t.Errorf("output did not match regexp /%s/\n> output\n%s\n", r, output)
441-
return
442-
}
486+
assert.Equal(t, tt.wantStderr, output.Stderr())
443487
reg.Verify(t)
444488
})
445489
}

0 commit comments

Comments
 (0)