func getOrCreateMilestoneForCommit( config Config, owner string, repo string, sha string, ) (*github.Milestone, error) { // Get the version associated with the given commit. v, err := version.GetByBranch(sha) if err != nil { return nil, err } // Try to get the milestone. milestone, err := milestoneForVersion(config, owner, repo, v) if err != nil { return nil, err } if milestone == nil { // Create the milestone when not found. milestone, _, err := createMilestone(config, owner, repo, v) return milestone, err } // Milestone found, return it. return milestone, nil }
func getOrCreateMilestoneForCommit( config *moduleConfig, owner string, repo string, sha string, ) (*github.Milestone, error) { // Get the version associated with the given commit. v, err := version.GetByBranch(sha) if err != nil { return nil, err } // Get or create the milestone for the given title. var ( client = ghutil.NewClient(config.Token) title = milestoneTitle(v) ) milestone, _, err := ghissues.GetOrCreateMilestoneForTitle(client, owner, repo, title) return milestone, err }
func runMain() (err error) { // Load repo config. gitConfig, err := git.LoadConfig() if err != nil { return err } var ( remote = gitConfig.RemoteName trunkBranch = gitConfig.TrunkBranchName releaseBranch = gitConfig.ReleaseBranchName stagingBranch = gitConfig.StagingBranchName ) // Fetch the remote repository. if !flagNoFetch { task := "Fetch the remote repository" log.Run(task) if err := git.UpdateRemotes(remote); err != nil { return errs.NewError(task, err) } } // Make sure trunk is up to date. task := fmt.Sprintf("Make sure that branch '%v' is up to date", trunkBranch) if err := git.CheckOrCreateTrackingBranch(trunkBranch, remote); err != nil { return errs.NewError(task, err) } // Make sure the staging branch is up to date, in case it exists. // // We check stage here as well since it is otherwise checked later // in releases.ListNewTrunkCommits(), which is usually called in // release.PromptUserToConfirmStart(). task = fmt.Sprintf("Make sure that branch '%v' is up to date", stagingBranch) if err := git.CheckOrCreateTrackingBranch(stagingBranch, remote); err != nil { // The staging branch actually doesn't need to exist. if _, ok := err.(*git.ErrRefNotFound); !ok { return errs.NewError(task, err) } } // Make sure that the release branch does not exist. task = fmt.Sprintf("Make sure that branch '%v' does not exist", releaseBranch) if err := git.EnsureBranchNotExist(releaseBranch, remote); err != nil { return errs.NewError(task, err) } // Get the current trunk version string. task = "Get the current trunk version string" trunkVersion, err := version.GetByBranch(trunkBranch) if err != nil { return errs.NewError(task, err) } // Get the next trunk version (the future release version). var nextTrunkVersion *version.Version if !flagNextTrunk.Zero() { // Make sure it's only major, minor and patch that are set. // Make sure the new version is actually incrementing the current one. var ( current = trunkVersion next = flagNextTrunk ) var part string switch { case len(next.Pre) != 0: part = "Pre" case len(next.Build) != 0: part = "Build" } if part != "" { return fmt.Errorf("invalid future version string: %v version part cannot be set", part) } if current.GE(next.Version) { return fmt.Errorf("future version string not an increment: %v <= %v", next, current) } nextTrunkVersion = &flagNextTrunk } else { nextTrunkVersion = trunkVersion.IncrementMinor() } // Make sure the next trunk version has the right format. nextTrunkVersion, err = nextTrunkVersion.ToTrunkVersion() if err != nil { return err } // Fetch the stories from the issue tracker. tracker, err := modules.GetIssueTracker() if err != nil { return errs.NewError(task, err) } release := tracker.NextRelease(trunkVersion, nextTrunkVersion) // Prompt the user to confirm the release. fmt.Printf(` You are about to start a new release branch. The relevant version strings are: current release (current trunk version): %v future release (next trunk version): %v `, trunkVersion, nextTrunkVersion) ok, err := release.PromptUserToConfirmStart() if err != nil { return err } if !ok { fmt.Println("\nYour wish is my command, exiting now!") return nil } fmt.Println() // Create the release branch on top of the trunk branch. task = fmt.Sprintf("Create branch '%v' on top of branch '%v'", releaseBranch, trunkBranch) log.Run(task) if err := git.Branch(releaseBranch, trunkBranch); err != nil { return errs.NewError(task, err) } defer action.RollbackTaskOnError(&err, task, action.ActionFunc(func() error { task := fmt.Sprintf("Delete branch '%v'", releaseBranch) if err := git.Branch("-D", releaseBranch); err != nil { errs.NewError(task, err) } return nil })) // Bump the release branch version. testingVersion, err := trunkVersion.ToTestingVersion() if err != nil { return err } task = fmt.Sprintf("Bump version (branch '%v' -> %v)", releaseBranch, testingVersion) log.Run(task) _, err = version.SetForBranch(testingVersion, releaseBranch) if err != nil { return errs.NewError(task, err) } // No need for a rollback function here, git branch -d specified as a rollback // for the previous step will take care of deleting this change as well. // Bump the trunk branch version. task = fmt.Sprintf("Bump version (branch '%v' -> %v)", trunkBranch, nextTrunkVersion) log.Run(task) act, err := version.SetForBranch(nextTrunkVersion, trunkBranch) if err != nil { return errs.NewError(task, err) } defer action.RollbackTaskOnError(&err, task, act) // Initialise the next release in the code review tool. codeReviewTool, err := modules.GetCodeReviewTool() if err != nil { return err } act, err = codeReviewTool.NewRelease(nextTrunkVersion).Initialise() if err != nil { return err } defer action.RollbackTaskOnError(&err, task, act) // Start the release in the issue tracker. act, err = release.Start() if err != nil { return err } defer action.RollbackTaskOnError(&err, task, act) // Push the modified branches. task = "Push changes to the remote repository" log.Run(task) err = git.Push(remote, trunkBranch+":"+trunkBranch, releaseBranch+":"+releaseBranch) if err != nil { return errs.NewError(task, err) } return nil }
func Stage(options *StageOptions) (act action.Action, err error) { // Rollback machinery. chain := action.NewActionChain() defer chain.RollbackOnError(&err) // Make sure opts are not nil. if options == nil { options = DefaultStageOptions } // Load git config. gitConfig, err := git.LoadConfig() if err != nil { return nil, err } var ( remoteName = gitConfig.RemoteName releaseBranch = gitConfig.ReleaseBranchName stagingBranch = gitConfig.StagingBranchName ) // Instantiate the issue tracker. tracker, err := modules.GetIssueTracker() if err != nil { return nil, err } // Get the current branch. task := "Get the current branch" currentBranch, err := gitutil.CurrentBranch() if err != nil { return nil, err } // Cannot be on the release branch, it will be deleted. task = fmt.Sprintf("Make sure that branch '%v' is not checked out", releaseBranch) if currentBranch == releaseBranch { return nil, errs.NewError( task, fmt.Errorf("cannot stage the release while on branch '%v'", releaseBranch)) } // Fetch the remote repository. if !options.SkipFetch { task = "Fetch the remote repository" log.Run(task) if err := git.UpdateRemotes(remoteName); err != nil { return nil, errs.NewError(task, err) } } // Make sure that the local release branch exists and is up to date. task = fmt.Sprintf("Make sure that branch '%v' is up to date", releaseBranch) log.Run(task) if err := git.CheckOrCreateTrackingBranch(releaseBranch, remoteName); err != nil { return nil, errs.NewError(task, err) } // Read the current release version. task = "Read the current release version" releaseVersion, err := version.GetByBranch(releaseBranch) if err != nil { return nil, errs.NewError(task, err) } // Make sure the release is stageable. release := tracker.RunningRelease(releaseVersion) if err := release.EnsureStageable(); err != nil { return nil, err } // Make sure there are no commits being left behind, // e.g. make sure no commits are forgotten on the trunk branch, // i.e. make sure that everything necessary was cherry-picked. if err := checkCommits(tracker, release, releaseBranch); err != nil { return nil, err } // Reset the staging branch to point to the newly created tag. task = fmt.Sprintf("Reset branch '%v' to point to branch '%v'", stagingBranch, releaseBranch) log.Run(task) act, err = git.CreateOrResetBranch(stagingBranch, releaseBranch) if err != nil { return nil, errs.NewError(task, err) } chain.PushTask(task, act) // Delete the local release branch. task = fmt.Sprintf("Delete branch '%v'", releaseBranch) log.Run(task) if err := git.Branch("-D", releaseBranch); err != nil { return nil, errs.NewError(task, err) } chain.PushTask(task, action.ActionFunc(func() error { task := fmt.Sprintf("Recreate branch '%v'", releaseBranch) // In case the release branch exists locally, do nothing. // This might look like an extra and useless check, but it looks like // the final git push at the end of the command function actually creates // the release branch locally when it is aborted from the pre-push hook. // Not sure why and how that is happening. exists, err := git.LocalBranchExists(releaseBranch) if err != nil { return errs.NewError(task, err) } if exists { return nil } // In case the branch indeed does not exist, create it. if err := git.Branch(releaseBranch, remoteName+"/"+releaseBranch); err != nil { return errs.NewError(task, err) } return nil })) // Update the version string on the staging branch. stagingVersion, err := releaseVersion.ToStageVersion() if err != nil { return nil, err } task = fmt.Sprintf("Bump version (branch '%v' -> %v)", stagingBranch, stagingVersion) log.Run(task) act, err = version.SetForBranch(stagingVersion, stagingBranch) if err != nil { return nil, errs.NewError(task, err) } chain.PushTask(task, act) // Stage the release in the issue tracker. act, err = release.Stage() if err != nil { return nil, err } chain.Push(act) // Push to create the tag, reset client and delete release in the remote repository. task = "Push changes to the remote repository" log.Run(task) err = git.Push(remoteName, "-f", // Use the Force, Luke. ":"+releaseBranch, // Delete the release branch. stagingBranch+":"+stagingBranch) // Push the staging branch. if err != nil { return nil, err } return chain, nil }
func runMain() (err error) { // Load repo config. gitConfig, err := git.LoadConfig() if err != nil { return err } var ( remoteName = gitConfig.RemoteName releaseBranch = gitConfig.ReleaseBranchName ) // Make sure that the local release branch exists. task := "Make sure that the local release branch exists" if err := git.CheckOrCreateTrackingBranch(releaseBranch, remoteName); err != nil { return errs.NewError(task, err) } // Get the current release version string. task = "Get the release branch version string" releaseVersion, err := version.GetByBranch(releaseBranch) if err != nil { return errs.NewError(task, err) } // Get the stories associated with the current release. task = "Fetch the stories associated with the current release" log.Run(task) tracker, err := modules.GetIssueTracker() if err != nil { return errs.NewError(task, err) } release := tracker.RunningRelease(releaseVersion) stories, err := release.Stories() if err != nil { return errs.NewError(task, err) } if len(stories) == 0 { return errs.NewError(task, errors.New("no relevant stories found")) } // Get the story changes. task = "Collect the story changes" log.Run(task) groups, err := changes.StoryChanges(stories) if err != nil { return errs.NewError(task, err) } // Just return in case there are no relevant commits found. if len(groups) == 0 { return errs.NewError(task, errors.New("no relevant commits found")) } // Sort the change groups. groups = changes.SortStoryChanges(groups, stories) if flagToCherryPick { groups, err = releases.StoryChangesToCherryPick(groups) if err != nil { return errs.NewError(task, err) } } // Dump the change details into the console. if !flagPorcelain { fmt.Println() } changes.DumpStoryChanges(os.Stdout, groups, tracker, flagPorcelain) if !flagPorcelain { fmt.Println() } 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 }
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 { if err := git.UpdateRemotes(remoteName); err != nil { return 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) 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) currentBranch, err := git.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) } stagingVersion, err := version.GetByBranch(stagingBranch) if err != nil { return errs.NewError(task, err) } release, err := tracker.RunningRelease(stagingVersion) if err != nil { return err } if err := release.EnsureReleasable(); 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 { 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, skipping ...") } // 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 ...") } } // 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 }