Skip to content

Commit bccc93a

Browse files
author
Nate Smith
authored
Merge pull request cli#1423 from cli/migrate-repo-fork
isolate repo fork command and tweak usage
2 parents 9035418 + 75cce0a commit bccc93a

File tree

10 files changed

+808
-662
lines changed

10 files changed

+808
-662
lines changed

command/repo.go

Lines changed: 11 additions & 245 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,11 @@
11
package command
22

33
import (
4-
"errors"
5-
"fmt"
6-
"net/url"
7-
"strings"
8-
"time"
9-
104
"github.com/MakeNowJust/heredoc"
11-
"github.com/cli/cli/api"
12-
"github.com/cli/cli/git"
13-
"github.com/cli/cli/internal/ghrepo"
14-
"github.com/cli/cli/internal/run"
15-
"github.com/cli/cli/pkg/prompt"
16-
"github.com/cli/cli/utils"
175
"github.com/spf13/cobra"
186
)
197

208
func init() {
21-
repoCmd.AddCommand(repoForkCmd)
22-
repoForkCmd.Flags().String("clone", "prompt", "Clone fork: {true|false|prompt}")
23-
repoForkCmd.Flags().String("remote", "prompt", "Add remote for fork: {true|false|prompt}")
24-
repoForkCmd.Flags().Lookup("clone").NoOptDefVal = "true"
25-
repoForkCmd.Flags().Lookup("remote").NoOptDefVal = "true"
26-
279
repoCmd.AddCommand(repoCreditsCmd)
2810
repoCreditsCmd.Flags().BoolP("static", "s", false, "Print a static version of the credits")
2911
}
@@ -45,243 +27,27 @@ A repository can be supplied as an argument in any of the following formats:
4527
- by URL, e.g. "https://github.com/OWNER/REPO"`},
4628
}
4729

48-
var repoForkCmd = &cobra.Command{
49-
Use: "fork [<repository>]",
50-
Short: "Create a fork of a repository",
51-
Long: `Create a fork of a repository.
52-
53-
With no argument, creates a fork of the current repository. Otherwise, forks the specified repository.`,
54-
RunE: repoFork,
55-
}
56-
5730
var repoCreditsCmd = &cobra.Command{
5831
Use: "credits [<repository>]",
5932
Short: "View credits for a repository",
6033
Example: heredoc.Doc(`
61-
# view credits for the current repository
62-
$ gh repo credits
63-
64-
# view credits for a specific repository
65-
$ gh repo credits cool/repo
34+
# view credits for the current repository
35+
$ gh repo credits
6636
67-
# print a non-animated thank you
68-
$ gh repo credits -s
69-
70-
# pipe to just print the contributors, one per line
71-
$ gh repo credits | cat
72-
`),
37+
# view credits for a specific repository
38+
$ gh repo credits cool/repo
39+
40+
# print a non-animated thank you
41+
$ gh repo credits -s
42+
43+
# pipe to just print the contributors, one per line
44+
$ gh repo credits | cat
45+
`),
7346
Args: cobra.MaximumNArgs(1),
7447
RunE: repoCredits,
7548
Hidden: true,
7649
}
7750

78-
var Since = func(t time.Time) time.Duration {
79-
return time.Since(t)
80-
}
81-
82-
func repoFork(cmd *cobra.Command, args []string) error {
83-
ctx := contextForCommand(cmd)
84-
85-
clonePref, err := cmd.Flags().GetString("clone")
86-
if err != nil {
87-
return err
88-
}
89-
remotePref, err := cmd.Flags().GetString("remote")
90-
if err != nil {
91-
return err
92-
}
93-
94-
apiClient, err := apiClientForContext(ctx)
95-
if err != nil {
96-
return fmt.Errorf("unable to create client: %w", err)
97-
}
98-
99-
var repoToFork ghrepo.Interface
100-
inParent := false // whether or not we're forking the repo we're currently "in"
101-
if len(args) == 0 {
102-
baseRepo, err := determineBaseRepo(apiClient, cmd, ctx)
103-
if err != nil {
104-
return fmt.Errorf("unable to determine base repository: %w", err)
105-
}
106-
inParent = true
107-
repoToFork = baseRepo
108-
} else {
109-
repoArg := args[0]
110-
111-
if utils.IsURL(repoArg) {
112-
parsedURL, err := url.Parse(repoArg)
113-
if err != nil {
114-
return fmt.Errorf("did not understand argument: %w", err)
115-
}
116-
117-
repoToFork, err = ghrepo.FromURL(parsedURL)
118-
if err != nil {
119-
return fmt.Errorf("did not understand argument: %w", err)
120-
}
121-
122-
} else if strings.HasPrefix(repoArg, "git@") {
123-
parsedURL, err := git.ParseURL(repoArg)
124-
if err != nil {
125-
return fmt.Errorf("did not understand argument: %w", err)
126-
}
127-
repoToFork, err = ghrepo.FromURL(parsedURL)
128-
if err != nil {
129-
return fmt.Errorf("did not understand argument: %w", err)
130-
}
131-
} else {
132-
repoToFork, err = ghrepo.FromFullName(repoArg)
133-
if err != nil {
134-
return fmt.Errorf("argument error: %w", err)
135-
}
136-
}
137-
}
138-
139-
if !connectedToTerminal(cmd) {
140-
if (inParent && remotePref == "prompt") || (!inParent && clonePref == "prompt") {
141-
return errors.New("--remote or --clone must be explicitly set when not attached to tty")
142-
}
143-
}
144-
145-
greenCheck := utils.Green("✓")
146-
stderr := colorableErr(cmd)
147-
s := utils.Spinner(stderr)
148-
stopSpinner := func() {}
149-
150-
if connectedToTerminal(cmd) {
151-
loading := utils.Gray("Forking ") + utils.Bold(utils.Gray(ghrepo.FullName(repoToFork))) + utils.Gray("...")
152-
s.Suffix = " " + loading
153-
s.FinalMSG = utils.Gray(fmt.Sprintf("- %s\n", loading))
154-
utils.StartSpinner(s)
155-
stopSpinner = func() {
156-
utils.StopSpinner(s)
157-
158-
}
159-
}
160-
161-
forkedRepo, err := api.ForkRepo(apiClient, repoToFork)
162-
if err != nil {
163-
stopSpinner()
164-
return fmt.Errorf("failed to fork: %w", err)
165-
}
166-
167-
stopSpinner()
168-
169-
// This is weird. There is not an efficient way to determine via the GitHub API whether or not a
170-
// given user has forked a given repo. We noticed, also, that the create fork API endpoint just
171-
// returns the fork repo data even if it already exists -- with no change in status code or
172-
// anything. We thus check the created time to see if the repo is brand new or not; if it's not,
173-
// we assume the fork already existed and report an error.
174-
createdAgo := Since(forkedRepo.CreatedAt)
175-
if createdAgo > time.Minute {
176-
if connectedToTerminal(cmd) {
177-
fmt.Fprintf(stderr, "%s %s %s\n",
178-
utils.Yellow("!"),
179-
utils.Bold(ghrepo.FullName(forkedRepo)),
180-
"already exists")
181-
} else {
182-
fmt.Fprintf(stderr, "%s already exists", ghrepo.FullName(forkedRepo))
183-
return nil
184-
}
185-
} else {
186-
if connectedToTerminal(cmd) {
187-
fmt.Fprintf(stderr, "%s Created fork %s\n", greenCheck, utils.Bold(ghrepo.FullName(forkedRepo)))
188-
}
189-
}
190-
191-
if (inParent && remotePref == "false") || (!inParent && clonePref == "false") {
192-
return nil
193-
}
194-
195-
if inParent {
196-
remotes, err := ctx.Remotes()
197-
if err != nil {
198-
return err
199-
}
200-
if remote, err := remotes.FindByRepo(forkedRepo.RepoOwner(), forkedRepo.RepoName()); err == nil {
201-
if connectedToTerminal(cmd) {
202-
fmt.Fprintf(stderr, "%s Using existing remote %s\n", greenCheck, utils.Bold(remote.Name))
203-
}
204-
return nil
205-
}
206-
207-
remoteDesired := remotePref == "true"
208-
if remotePref == "prompt" {
209-
err = prompt.Confirm("Would you like to add a remote for the fork?", &remoteDesired)
210-
if err != nil {
211-
return fmt.Errorf("failed to prompt: %w", err)
212-
}
213-
}
214-
if remoteDesired {
215-
remoteName := "origin"
216-
217-
remotes, err := ctx.Remotes()
218-
if err != nil {
219-
return err
220-
}
221-
if _, err := remotes.FindByName(remoteName); err == nil {
222-
renameTarget := "upstream"
223-
renameCmd := git.GitCommand("remote", "rename", remoteName, renameTarget)
224-
err = run.PrepareCmd(renameCmd).Run()
225-
if err != nil {
226-
return err
227-
}
228-
if connectedToTerminal(cmd) {
229-
fmt.Fprintf(stderr, "%s Renamed %s remote to %s\n", greenCheck, utils.Bold(remoteName), utils.Bold(renameTarget))
230-
}
231-
}
232-
233-
forkedRepoCloneURL := formatRemoteURL(cmd, forkedRepo)
234-
235-
_, err = git.AddRemote(remoteName, forkedRepoCloneURL)
236-
if err != nil {
237-
return fmt.Errorf("failed to add remote: %w", err)
238-
}
239-
240-
if connectedToTerminal(cmd) {
241-
fmt.Fprintf(stderr, "%s Added remote %s\n", greenCheck, utils.Bold(remoteName))
242-
}
243-
}
244-
} else {
245-
cloneDesired := clonePref == "true"
246-
if clonePref == "prompt" {
247-
err = prompt.Confirm("Would you like to clone the fork?", &cloneDesired)
248-
if err != nil {
249-
return fmt.Errorf("failed to prompt: %w", err)
250-
}
251-
}
252-
if cloneDesired {
253-
forkedRepoCloneURL := formatRemoteURL(cmd, forkedRepo)
254-
cloneDir, err := git.RunClone(forkedRepoCloneURL, []string{})
255-
if err != nil {
256-
return fmt.Errorf("failed to clone fork: %w", err)
257-
}
258-
259-
// TODO This is overly wordy and I'd like to streamline this.
260-
cfg, err := ctx.Config()
261-
if err != nil {
262-
return err
263-
}
264-
protocol, err := cfg.Get("", "git_protocol")
265-
if err != nil {
266-
return err
267-
}
268-
269-
upstreamURL := ghrepo.FormatRemoteURL(repoToFork, protocol)
270-
271-
err = git.AddUpstreamRemote(upstreamURL, cloneDir)
272-
if err != nil {
273-
return err
274-
}
275-
276-
if connectedToTerminal(cmd) {
277-
fmt.Fprintf(stderr, "%s Cloned fork\n", greenCheck)
278-
}
279-
}
280-
}
281-
282-
return nil
283-
}
284-
28551
func repoCredits(cmd *cobra.Command, args []string) error {
28652
return credits(cmd, args)
28753
}

0 commit comments

Comments
 (0)