예제 #1
0
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
}
예제 #2
0
// 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
}
예제 #3
0
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
}
예제 #4
0
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
}