From 35edd30d42f93e7e5144f1febccf3b06f96a5180 Mon Sep 17 00:00:00 2001 From: Bruno Meneguele Date: Wed, 16 Dec 2020 18:04:21 -0300 Subject: [PATCH] fork: wait for the actual fork process completion Today, `lab fork` doesn't check if the fork on Gitlab system is indeed finished, it just assumes so and move on. However, some projects are big enough to not satisfy it, for instance: the linux kernel. A project containing the linux kernel source code may take _minutes_ to fork, and the user will be prompted with an error if the "clone" is not skipped with --skip-clone. This patch forces the `lab fork` command to wait the fork completion on Gitlab system and also adds the "--no-wait" flag if the user really don't want to wait for anything. Signed-off-by: Bruno Meneguele --- cmd/fork.go | 28 ++++++++++++++++++++++------ internal/gitlab/gitlab.go | 28 ++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/cmd/fork.go b/cmd/fork.go index 3cdf168a..f40ba796 100644 --- a/cmd/fork.go +++ b/cmd/fork.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "log" "strings" @@ -10,8 +11,11 @@ import ( lab "github.com/zaquestion/lab/internal/gitlab" ) -var skipClone = false -var forkData lab.ForkStruct +var ( + skipClone = false + waitFork = true + forkData lab.ForkStruct +) // forkCmd represents the fork command var forkCmd = &cobra.Command{ @@ -22,6 +26,8 @@ var forkCmd = &cobra.Command{ PersistentPreRun: LabPersistentPreRun, Run: func(cmd *cobra.Command, args []string) { skipClone, _ = cmd.Flags().GetBool("skip-clone") + noWaitFork, _ := cmd.Flags().GetBool("no-wait") + waitFork = !noWaitFork forkData.TargetName, _ = cmd.Flags().GetString("name") forkData.TargetNamespace, _ = cmd.Flags().GetString("namespace") forkData.TargetPath, _ = cmd.Flags().GetString("path") @@ -60,9 +66,13 @@ func forkFromOrigin(cmd *cobra.Command, args []string) { log.Fatal(err) } forkData.SrcProject = project - forkRemoteURL, err := lab.Fork(forkData, useHTTP) + forkRemoteURL, err := lab.Fork(forkData, useHTTP, waitFork) if err != nil { - log.Fatal(err) + if err.Error() == "not finished" { + fmt.Println("This fork is not ready yet and might take some minutes.") + } else { + log.Fatal(err) + } } name := determineForkRemote(project) @@ -75,9 +85,14 @@ func forkToUpstream(cmd *cobra.Command, args []string) { forkData.SrcProject = args[0] // lab.Fork doesn't have access to the useHTTP var, so we need to pass // this info to that, so the process works correctly. - _, err := lab.Fork(forkData, useHTTP) + _, err := lab.Fork(forkData, useHTTP, waitFork) if err != nil { - log.Fatal(err) + if err.Error() == "not finished" && !skipClone { + fmt.Println("This fork is not ready yet and might take some minutes.") + skipClone = true + } else { + log.Fatal(err) + } } if !skipClone { @@ -112,6 +127,7 @@ func determineForkRemote(project string) string { func init() { forkCmd.Flags().BoolP("skip-clone", "s", false, "skip clone after remote fork") + forkCmd.Flags().Bool("no-wait", false, "don't wait for forking operation to finish") forkCmd.Flags().StringP("name", "n", "", "fork project with a different name") forkCmd.Flags().StringP("namespace", "m", "", "fork project in a different namespace") forkCmd.Flags().StringP("path", "p", "", "fork project with a different path") diff --git a/internal/gitlab/gitlab.go b/internal/gitlab/gitlab.go index 09ac0d5c..4768be8c 100644 --- a/internal/gitlab/gitlab.go +++ b/internal/gitlab/gitlab.go @@ -224,7 +224,7 @@ func (fs ForkStruct) isCustomTargetSet() bool { } // Fork creates a user fork of a GitLab project using the specified protocol -func Fork(data ForkStruct, useHTTP bool) (string, error) { +func Fork(data ForkStruct, useHTTP bool, wait bool) (string, error) { if !strings.Contains(data.SrcProject, "/") { return "", errors.New("remote must include namespace") } @@ -247,6 +247,10 @@ func Fork(data ForkStruct, useHTTP bool) (string, error) { return "", err } + // Now that we have the "wait" opt, don't let the user in the hope that + // something is running. + fmt.Printf("Forking %s project...\n", data.SrcProject) + var forkOpts *gitlab.ForkProjectOptions = nil if data.isCustomTargetSet() { forkOpts = &gitlab.ForkProjectOptions{ @@ -260,11 +264,31 @@ func Fork(data ForkStruct, useHTTP bool) (string, error) { return "", err } + // Busy-wait approach for checking the import_status of the fork. + // References: + // https://docs.gitlab.com/ce/api/projects.html#fork-project + // https://docs.gitlab.com/ee/api/project_import_export.html#import-status + status, _, err := lab.ProjectImportExport.ImportStatus(fork.ID, nil) + if wait { + for { + if status.ImportStatus == "finished" { + break + } + status, _, err = lab.ProjectImportExport.ImportStatus(fork.ID, nil) + if err != nil { + log.Fatal(err) + } + time.Sleep(2 * time.Second) + } + } else if status.ImportStatus != "finished" { + err = errors.New("not finished") + } + urlToRepo := fork.SSHURLToRepo if useHTTP { urlToRepo = fork.HTTPURLToRepo } - return urlToRepo, nil + return urlToRepo, err } // MRCreate opens a merge request on GitLab