// doInstall performs the common step that both install and upgrade need to do. // // Given a GitHub release, it downloads and unpacks the fitting artifacts // and replaces the current executables with the ones just downloaded. func doInstall( client *github.Client, owner string, repo string, assets []github.ReleaseAsset, version string, dstDir string, ) (err error) { // Choose the asset to be downloaded. task := "Pick the most suitable release asset" var ( assetName = getAssetName(version) assetURL string ) for _, asset := range assets { if *asset.Name == assetName { assetURL = *asset.BrowserDownloadURL } } if assetURL == "" { return errs.NewError(task, errors.New("no suitable release asset found")) } // Make sure the destination folder exists. task = "Make sure the destination directory exists" dstDir, act, err := ensureDstDirExists(dstDir) if err != nil { return errs.NewError(task, err) } defer action.RollbackOnError(&err, act) // Download the selected release asset. return downloadAndInstallAsset(assetName, assetURL, dstDir) }
func runMain(cmd *gocli.Command) (err error) { // Validate CL flags. task := "Check the command line flags" switch { case flagSkeleton == "" && !flagNoSkeleton: cmd.Usage() return errs.NewError( task, errors.New("-no_skeleton must be specified when no skeleton is given")) case flagSkeletonOnly && flagSkeleton == "": cmd.Usage() return errs.NewError( task, errors.New("-skeleton must be specified when -skeleton_only is set")) } // Make sure the local config directory exists. act, err := ensureLocalConfigDirectoryExists() if err != nil { return err } defer action.RollbackOnError(&err, act) // Set up the global and local configuration file unless -skeleton_only. if !flagSkeletonOnly { if err := assembleAndWriteConfig(); err != nil { return err } } // Install the skeleton into the local config directory if desired. if skeleton := flagSkeleton; skeleton != "" { if err := getAndPourSkeleton(skeleton); err != nil { return err } } fmt.Println() log.Log("Successfully bootstrapped the repository for SalsaFlow") log.NewLine("Do not forget to commit modified configuration files!") return nil }
func postBranch(parentBranch string) (err error) { // Load the git-related config. gitConfig, err := git.LoadConfig() if err != nil { return err } var ( remoteName = gitConfig.RemoteName ) // Get the current branch name. currentBranch, err := gitutil.CurrentBranch() if err != nil { return err } if !flagNoFetch { // Fetch the remote repository. task := "Fetch the remote repository" log.Run(task) if err := git.UpdateRemotes(remoteName); err != nil { return errs.NewError(task, err) } } // Make sure the parent branch is up to date. task := fmt.Sprintf("Make sure reference '%v' is up to date", parentBranch) log.Run(task) if err := git.EnsureBranchSynchronized(parentBranch, remoteName); err != nil { return errs.NewError(task, err) } // Make sure the current branch is up to date. task = fmt.Sprintf("Make sure branch '%v' is up to date", currentBranch) log.Run(task) if err = git.EnsureBranchSynchronized(currentBranch, remoteName); err != nil { return errs.NewError(task, err) } // Get the commits to be posted task = "Get the commits to be posted for code review" commits, err := git.ShowCommitRange(parentBranch + "..") if err != nil { return errs.NewError(task, err) } // Make sure there are no merge commits. if err := ensureNoMergeCommits(commits); err != nil { return err } // Prompt the user to confirm. if err := promptUserToConfirmCommits(commits); err != nil { return err } // Rebase the current branch on top the parent branch. if !flagNoRebase { commits, err = rebase(currentBranch, parentBranch) if err != nil { return err } } // Ensure the Story-Id tag is there. commits, _, err = ensureStoryId(commits) if err != nil { return err } // Get data on the current branch. task = fmt.Sprintf("Get data on branch '%v'", currentBranch) remoteCurrentExists, err := git.RemoteBranchExists(currentBranch, remoteName) if err != nil { return errs.NewError(task, err) } currentUpToDate, err := git.IsBranchSynchronized(currentBranch, remoteName) if err != nil { return errs.NewError(task, err) } // Merge the current branch into the parent branch unless -no_merge. pushTask := "Push the current branch" if flagNoMerge { // In case the user doesn't want to merge, // we need to push the current branch. if !remoteCurrentExists || !currentUpToDate { if err := push(remoteName, currentBranch); err != nil { return errs.NewError(pushTask, err) } } } else { // Still push the current branch if necessary. if remoteCurrentExists && !currentUpToDate { if err := push(remoteName, currentBranch); err != nil { return errs.NewError(pushTask, err) } } // Merge the branch into the parent branch mergeTask := fmt.Sprintf("Merge branch '%v' into branch '%v'", currentBranch, parentBranch) log.Run(mergeTask) act, err := merge(mergeTask, currentBranch, parentBranch) if err != nil { return err } // Push the parent branch. if err := push(remoteName, parentBranch); err != nil { // In case the push fails, we revert the merge as well. if err := act.Rollback(); err != nil { errs.Log(err) } return errs.NewError(mergeTask, err) } // Register a rollback function that just says that // a pushed merge cannot be reverted. defer action.RollbackOnError(&err, action.ActionFunc(func() error { log.Rollback(mergeTask) hint := "\nCannot revert merge that has already been pushed.\n" return errs.NewErrorWithHint( "Revert the merge", errors.New("merge commit already pushed"), hint) })) } // Post the review requests. if err := postCommitsForReview(commits); err != nil { return err } // In case there is no error, tell the user they can do next. return printFollowup() }
// pourSkeleton counts on the fact that skeleton is a valid skeleton // that is available in the local cache directory. func pourSkeleton(skeletonName string, localConfigDir string) (err error) { // Get the skeleton src path. cacheDir, err := cacheDirectoryAbsolutePath() if err != nil { return err } skeletonDir := filepath.Join(cacheDir, "github.com", skeletonName) // Make sure src is a directory, just to be sure. skeletonInfo, err := os.Stat(skeletonDir) if err != nil { return err } if !skeletonInfo.IsDir() { return fmt.Errorf("skeleton source path not a directory: %v", skeletonDir) } // Get the list of script files. srcScriptsDir := filepath.Join(skeletonDir, "scripts") scripts, err := filepath.Glob(srcScriptsDir + "/*") if err != nil { return err } if len(scripts) == 0 { log.Warn("No script files found in the skeleton repository") return nil } // Create the destination directory. dstScriptsDir := filepath.Join(localConfigDir, "scripts") if err := os.MkdirAll(dstScriptsDir, 0755); err != nil { return err } // Delete the directory on error. defer action.RollbackOnError(&err, action.ActionFunc(func() error { log.Rollback("Create the local scripts directory") if err := os.RemoveAll(dstScriptsDir); err != nil { return errs.NewError("Remove the local scripts directory", err) } return nil })) for _, script := range scripts { err := func(script string) error { // Skip directories. scriptInfo, err := os.Stat(script) if err != nil { return err } if scriptInfo.IsDir() { return nil } // Copy the file. filename := script[len(srcScriptsDir)+1:] fmt.Println("---> Copy", filepath.Join("scripts", filename)) srcFd, err := os.Open(script) if err != nil { return err } defer srcFd.Close() dstPath := filepath.Join(dstScriptsDir, filename) dstFd, err := os.OpenFile( dstPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, scriptInfo.Mode()) if err != nil { return err } defer dstFd.Close() _, err = io.Copy(dstFd, srcFd) return err }(script) if err != nil { return err } } return nil }
func runMain() (err error) { // Load repo config. gitConfig, err := git.LoadConfig() if err != nil { return err } var ( remoteName = gitConfig.RemoteName stagingBranch = gitConfig.StagingBranchName stableBranch = gitConfig.StableBranchName ) // Fetch the repository. if !flagNoFetch { task := "Fetch the remote repository" log.Run(task) if err := git.UpdateRemotes(remoteName); err != nil { return errs.NewError(task, err) } } // Check branches. checkBranch := func(branchName string) error { // Make sure the branch exists. task := fmt.Sprintf("Make sure that branch '%v' exists and is up to date", branchName) log.Run(task) if err := git.CheckOrCreateTrackingBranch(branchName, remoteName); err != nil { return errs.NewError(task, err) } // Make sure we are not on the branch. task = fmt.Sprintf("Make sure that branch '%v' is not checked out", branchName) log.Run(task) currentBranch, err := gitutil.CurrentBranch() if err != nil { return errs.NewError(task, err) } if currentBranch == branchName { err := fmt.Errorf("cannot deploy while on branch '%v'", branchName) return errs.NewError(task, err) } return nil } for _, branch := range []string{stableBranch, stagingBranch} { if err := checkBranch(branch); err != nil { return err } } // Make sure the current staging branch can be released. task := fmt.Sprintf("Make sure that branch '%v' can be released", stagingBranch) log.Run(task) tracker, err := modules.GetIssueTracker() if err != nil { return errs.NewError(task, err) } codeReviewTool, err := modules.GetCodeReviewTool() if err != nil { return errs.NewError(task, err) } stagingVersion, err := version.GetByBranch(stagingBranch) if err != nil { return errs.NewError(task, err) } // Make sure the release can be closed in the issue tracker. issueTrackerRelease := tracker.RunningRelease(stagingVersion) if err := issueTrackerRelease.EnsureClosable(); err != nil { return err } // Make sure the release can be closed in the code review tool. codeReviewRelease := codeReviewTool.NewRelease(stagingVersion) if err := codeReviewRelease.EnsureClosable(); err != nil { return err } // Reset the stable branch to point to stage. task = fmt.Sprintf("Reset branch '%v' to point to branch '%v'", stableBranch, stagingBranch) log.Run(task) act, err := git.CreateOrResetBranch(stableBranch, stagingBranch) if err != nil { return errs.NewError(task, err) } defer action.RollbackTaskOnError(&err, task, act) // Bump version for the stable branch. stableVersion, err := stagingVersion.ToStableVersion() if err != nil { return err } task = fmt.Sprintf("Bump version (branch '%v' -> %v)", stableBranch, stableVersion) log.Run(task) act, err = version.SetForBranch(stableVersion, stableBranch) if err != nil { return errs.NewError(task, err) } defer action.RollbackTaskOnError(&err, task, act) // Tag the stable branch. tag := stableVersion.ReleaseTagString() task = fmt.Sprintf("Tag branch '%v' with tag '%v'", stableBranch, tag) log.Run(task) if err := git.Tag(tag, stableBranch); err != nil { return errs.NewError(task, err) } defer action.RollbackTaskOnError(&err, task, action.ActionFunc(func() error { task := fmt.Sprintf("Delete tag '%v'", tag) if err := git.Tag("-d", tag); err != nil { return errs.NewError(task, err) } return nil })) // Generate the release notes. // We try to do as much as possible before pushing. task = fmt.Sprintf("Generate release notes for version '%v'", stableVersion) log.Run(task) rnm, err := modules.GetReleaseNotesManager() if err != nil { if _, ok := err.(*modules.ErrModuleNotSet); !ok { return errs.NewError(task, err) } } var nts *common.ReleaseNotes // rnm will be nil in case the module is disabled. if rnm != nil { // Get the relevant stories. stories, err := tracker.ListStoriesByRelease(stableVersion) if err != nil { return errs.NewError(task, err) } // Generate the release notes. nts = notes.GenerateReleaseNotes(stableVersion, stories) } else { log.Log("Release notes module disabled, not doing anything") } // Close the release in the issue tracker. act, err = issueTrackerRelease.Close() if err != nil { return err } defer action.RollbackOnError(&err, act) // Close the release in the code review tool. act, err = codeReviewRelease.Close() if err != nil { return err } defer action.RollbackOnError(&err, act) // Push the changes to the remote repository. task = "Push changes to the remote repository" log.Run(task) toPush := []string{ "--tags", fmt.Sprintf("%v:%v", stableBranch, stableBranch), } if err := git.PushForce(remoteName, toPush...); err != nil { return errs.NewError(task, err) } // Post the release notes. task = fmt.Sprintf("Post the release notes for version '%v'", stableVersion) if rnm != nil { log.Run(task) if _, err := rnm.PostReleaseNotes(nts); err != nil { errs.LogError(task, err) log.Warn("Failed to post the release notes, continuing anyway ...") } } // Tell the user we succeeded. color.Green("\n-----> Release %v deployed successfully!\n\n", stableVersion) color.Cyan("Let's check whether the next release branch can be staged already.\n") color.Cyan("In other words, we will try to run `release stage` and see what happens.\n\n") // Now we proceed to the staging step. We do not roll back // the previous changes on error since this is a separate step. tryToStageRunningRelease() return nil }