Exemplo n.º 1
0
func (release *nextRelease) PromptUserToConfirmStart() (bool, error) {
	// Fetch the stories already assigned to the release.
	var (
		ver       = release.trunkVersion
		verString = ver.BaseString()
		verLabel  = ver.ReleaseTagString()
	)
	task := fmt.Sprintf("Fetch the stories already assigned to release %v", verString)
	log.Run(task)
	assignedStories, err := release.tracker.storiesByRelease(ver)
	if err != nil {
		return false, errs.NewError(task, err)
	}

	// Collect the story IDs associated with the commits that
	// modified trunk since the last release.
	task = "Collect the stories that modified trunk since the last release"
	log.Run(task)
	storyIds, err := releases.ListStoryIdsToBeAssigned(release.tracker)
	if err != nil {
		return false, errs.NewError(task, err)
	}

	// Drop the stories that are already assigned.
	idSet := make(map[string]struct{}, len(assignedStories))
	for _, story := range assignedStories {
		idSet[strconv.Itoa(story.Id)] = struct{}{}
	}
	ids := make([]string, 0, len(storyIds))
	for _, id := range storyIds {
		if _, ok := idSet[id]; !ok {
			ids = append(ids, id)
		}
	}
	storyIds = ids

	// Fetch the collected stories from Pivotal Tracker, if necessary.
	var additionalStories []*pivotal.Story
	if len(storyIds) != 0 {
		task = "Fetch the collected stories from Pivotal Tracker"
		log.Run(task)

		var err error
		additionalStories, err = release.tracker.storiesById(storyIds)
		if len(additionalStories) == 0 && err != nil {
			return false, errs.NewError(task, err)
		}
		if len(additionalStories) != len(storyIds) {
			log.Warn("Some stories were dropped since they were not found in PT")
			log.NewLine("or they were filtered out by a story include label.")
		}

		// Drop stories already assigned to another release.
		notAssigned := make([]*pivotal.Story, 0, len(additionalStories))
	NotAssignedLoop:
		for _, story := range additionalStories {
			for _, label := range story.Labels {
				if isReleaseLabel(label.Name) {
					log.Warn(fmt.Sprintf(
						"Skipping story %v: modified trunk, but already labeled '%v'",
						story.Id, label.Name))
					continue NotAssignedLoop
				}
			}
			notAssigned = append(notAssigned, story)
		}
		additionalStories = notAssigned
	}

	// Check the Point Me label.
	task = "Make sure there are no unpointed stories"
	log.Run(task)
	pmLabel := release.tracker.config.PointMeLabel

	// Fetch the already assigned but unpointed stories.
	pmStories, err := release.tracker.searchStories(
		"label:\"%v\" AND label:\"%v\"", verLabel, pmLabel)
	if err != nil {
		return false, errs.NewError(task, err)
	}
	// Also add these that are to be added but are unpointed.
	for _, story := range additionalStories {
		if labeled(story, pmLabel) {
			pmStories = append(pmStories, story)
		}
	}
	// In case there are some unpointed stories, stop the release.
	if len(pmStories) != 0 {
		fmt.Println("\nThe following stories are still yet to be pointed:\n")
		err := storyprompt.ListStories(toCommonStories(pmStories, release.tracker), os.Stdout)
		if err != nil {
			return false, err
		}
		fmt.Println()
		return false, errs.NewError(task, errors.New("unpointed stories detected"))
	}

	// Print the summary into the console.
	summary := []struct {
		header  string
		stories []*pivotal.Story
	}{
		{
			"The following stories were manually assigned to the release:",
			assignedStories,
		},
		{
			"The following stories were added automatically (modified trunk):",
			additionalStories,
		},
	}
	for _, item := range summary {
		if len(item.stories) != 0 {
			fmt.Println()
			fmt.Println(item.header)
			fmt.Println()
			err := storyprompt.ListStories(toCommonStories(item.stories, release.tracker), os.Stdout)
			if err != nil {
				return false, err
			}
		}
	}

	// Ask the user to confirm.
	ok, err := prompt.Confirm(
		fmt.Sprintf(
			"\nAre you sure you want to start release %v?",
			release.trunkVersion.BaseString()), false)
	if err == nil {
		release.additionalStories = additionalStories
	}
	return ok, err
}
Exemplo n.º 2
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
}
Exemplo n.º 3
0
// PromptUserToConfirm is a part of common.NextRelease interface.
func (release *nextRelease) PromptUserToConfirmStart() (bool, error) {
	// Fetch the stories already assigned to the release.
	var (
		ver       = release.trunkVersion
		verString = ver.BaseString()
	)
	task := fmt.Sprintf("Fetch GitHub issues already assigned to release %v", verString)
	log.Run(task)
	assignedIssues, err := release.tracker.issuesByRelease(ver)
	if err != nil {
		return false, errs.NewError(task, err)
	}

	// Collect the issues that modified trunk since the last release.
	task = "Collect the issues that modified trunk since the last release"
	log.Run(task)
	issueNumsString, err := releases.ListStoryIdsToBeAssigned(release.tracker)
	if err != nil {
		return false, errs.NewError(task, err)
	}

	// Turn []string into []int.
	issueNums := make([]int, len(issueNumsString))
	for i, numString := range issueNumsString {
		// numString is #ISSUE_NUMBER.
		num, err := strconv.Atoi(numString[1:])
		if err != nil {
			panic(err)
		}
		issueNums[i] = num
	}

	// Drop the stories that are already assigned.
	numSet := make(map[int]struct{}, len(assignedIssues))
	for _, issue := range assignedIssues {
		numSet[*issue.Number] = struct{}{}
	}
	nums := make([]int, 0, len(issueNums))
	for _, num := range issueNums {
		if _, ok := numSet[num]; !ok {
			nums = append(nums, num)
		}
	}
	issueNums = nums

	// Fetch the collected issues from GitHub, if necessary.
	var additionalIssues []*github.Issue
	if len(issueNums) != 0 {
		task = "Fetch the collected issues from GitHub"
		log.Run(task)

		var err error
		additionalIssues, err = release.tracker.issuesByNumber(issueNums)
		if err != nil {
			return false, errs.NewError(task, err)
		}

		// Drop stories already assigned to another release.
		notAssigned := make([]*github.Issue, 0, len(additionalIssues))
		for _, issue := range additionalIssues {
			switch {
			case issue.Milestone == nil:
				notAssigned = append(notAssigned, issue)
			default:
				log.Warn(fmt.Sprintf(
					"Skipping issue #%v: modified trunk, but already assigned to milestone '%v'",
					*issue.Number, *issue.Milestone.Title))
			}
		}
		additionalIssues = notAssigned
	}

	// Print the summary into the console.
	summary := []struct {
		header string
		issues []*github.Issue
	}{
		{
			"The following issues were manually assigned to the release:",
			assignedIssues,
		},
		{
			"The following issues were added automatically (modified trunk):",
			additionalIssues,
		},
	}
	for _, item := range summary {
		if len(item.issues) != 0 {
			fmt.Println()
			fmt.Println(item.header)
			fmt.Println()
			err := storyprompt.ListStories(
				toCommonStories(item.issues, release.tracker), os.Stdout)
			if err != nil {
				return false, err
			}
		}
	}

	// Ask the user to confirm.
	ok, err := prompt.Confirm(
		fmt.Sprintf("\nAre you sure you want to start release %v?", verString), false)
	if err == nil {
		release.additionalIssues = additionalIssues
	}
	return ok, err
}