11package command
22
33import (
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
208func 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-
5730var 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-
28551func repoCredits (cmd * cobra.Command , args []string ) error {
28652 return credits (cmd , args )
28753}
0 commit comments