func (tracker *issueTracker) closeMilestone( v *version.Version, ) (m *github.Milestone, act action.Action, err error) { // Use a chain to group the actions. chain := action.NewActionChain() defer chain.RollbackOnError(&err) // Get the associated milestone. milestone, act, err := tracker.getOrCreateMilestone(v) if err != nil { return nil, nil, err } chain.Push(act) // Mark it as closed. var ( client = tracker.newClient() owner = tracker.config.GitHubOwner repo = tracker.config.GitHubRepository ) withRequestAllocated(func() { milestone, act, err = ghissues.CloseMilestone(client, owner, repo, milestone) }) if err != nil { return nil, nil, err } chain.Push(act) // Return the results. return milestone, chain, nil }
// Start is a part of common.NextRelease interface. func (release *nextRelease) Start() (act action.Action, err error) { // In case there are no additional issues, we are done. if len(release.additionalIssues) == 0 { return action.Noop, nil } // Set milestone for the additional issues. task := fmt.Sprintf( "Set milestone to '%v' for the issues added automatically", release.trunkVersion.BaseString()) log.Run(task) // Get the milestone corresponding to the release branch version string. chain := action.NewActionChain() defer chain.RollbackOnError(&err) milestone, act, err := release.tracker.getOrCreateMilestone(release.trunkVersion) if err != nil { return nil, errs.NewError(task, err) } chain.Push(act) // Update the issues. issues, act, err := release.tracker.updateIssues( release.additionalIssues, setMilestone(milestone), unsetMilestone()) if err != nil { return nil, errs.NewError(task, err) } chain.Push(act) release.additionalIssues = issues return chain, nil }
func implementedDialog(ctxs []*common.ReviewContext) (implemented bool, act action.Action, err error) { // Collect the affected stories. var ( stories = make([]common.Story, 0, len(ctxs)) storySet = make(map[string]struct{}, len(ctxs)) ) for _, ctx := range ctxs { story := ctx.Story // Skip unassigned commits. if story == nil { continue } rid := story.ReadableId() if _, ok := storySet[rid]; ok { continue } // Collect only the stories that are Being Implemented. // The transition doesn't make sense for other story states. if story.State() != common.StoryStateBeingImplemented { continue } storySet[rid] = struct{}{} stories = append(stories, story) } // Do nothing in case there are no stories left. if len(stories) == 0 { return false, nil, nil } // Prompt the user for confirmation. fmt.Println("\nIt is possible to mark the affected stories as implemented.") fmt.Println("The following stories were associated with one or more commits:\n") storyprompt.ListStories(stories, os.Stdout) fmt.Println() confirmed, err := prompt.Confirm( "Do you wish to mark these stories as implemented?", false) if err != nil { return false, nil, err } fmt.Println() if !confirmed { return false, nil, nil } // Always update as many stories as possible. var ( chain = action.NewActionChain() errUpdateFailed = errors.New("failed to update stories in the issue tracker") ex error ) for _, story := range stories { task := fmt.Sprintf("Mark story %v as implemented", story.ReadableId()) log.Run(task) act, err := story.MarkAsImplemented() if err != nil { errs.Log(errs.NewError(task, err)) ex = errUpdateFailed continue } chain.PushTask(task, act) } if ex != nil { if err := chain.Rollback(); err != nil { errs.Log(err) } return false, nil, ex } return true, chain, 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 }