func (tool *codeReviewTool) FinaliseRelease(v *version.Version) (action.Action, error) {
	// Get a GitHub client.
	config, err := LoadConfig()
	if err != nil {
		return nil, err
	}
	client := ghutil.NewClient(config.Token())

	owner, repo, err := git.ParseUpstreamURL()
	if err != nil {
		return nil, err
	}

	// Get the relevant review milestone.
	releaseString := v.BaseString()
	task := fmt.Sprintf("Get GitHub review milestone for release %v", releaseString)
	log.Run(task)
	milestone, err := milestoneForVersion(config, owner, repo, v)
	if err != nil {
		return nil, errs.NewError(task, err)
	}
	if milestone == nil {
		log.Warn(fmt.Sprintf(
			"Weird, GitHub review milestone for release %v not found", releaseString))
		return nil, nil
	}

	// Close the milestone unless there are some issues open.
	task = fmt.Sprintf(
		"Make sure the review milestone for release %v can be closed", releaseString)
	if num := *milestone.OpenIssues; num != 0 {
		return nil, errs.NewError(
			task,
			fmt.Errorf(
				"review milestone for release %v cannot be closed: %v issue(s) open",
				releaseString, num))
	}

	milestoneTask := fmt.Sprintf("Close GitHub review milestone for release %v", releaseString)
	log.Run(milestoneTask)
	milestone, _, err = client.Issues.EditMilestone(owner, repo, *milestone.Number, &github.Milestone{
		State: github.String("closed"),
	})
	if err != nil {
		return nil, errs.NewError(milestoneTask, err)
	}

	// Return a rollback function.
	return action.ActionFunc(func() error {
		log.Rollback(milestoneTask)
		task := fmt.Sprintf("Reopen GitHub review milestone for release %v", releaseString)
		_, _, err := client.Issues.EditMilestone(owner, repo, *milestone.Number, &github.Milestone{
			State: github.String("open"),
		})
		if err != nil {
			return errs.NewError(task, err)
		}
		return nil
	}), nil
}
func (tool *codeReviewTool) InitialiseRelease(v *version.Version) (action.Action, error) {
	// Get necessary config.
	config, err := LoadConfig()
	if err != nil {
		return nil, err
	}

	owner, repo, err := git.ParseUpstreamURL()
	if err != nil {
		return nil, err
	}

	// Check whether the review milestone exists or not.
	// People can create milestones manually, so this makes the thing more robust.
	task := fmt.Sprintf("Check whether GitHub review milestone exists for release %v", v.BaseString())
	log.Run(task)
	milestone, err := milestoneForVersion(config, owner, repo, v)
	if err != nil {
		return nil, errs.NewError(task, err)
	}
	if milestone != nil {
		// Milestone already exists, we are done.
		log.Log(fmt.Sprintf("GitHub review milestone '%v' already exists", milestoneTitle(v)))
		return nil, nil
	}

	// Create the review milestone.
	_, act, err := createMilestone(config, owner, repo, v)
	return act, err
}
func (tool *codeReviewTool) PostReviewRequests(
	ctxs []*common.ReviewContext,
	opts map[string]interface{},
) (err error) {

	// Load the GitHub config.
	config, err := LoadConfig()
	if err != nil {
		return err
	}

	// Get the GitHub owner and repository from the upstream URL.
	owner, repo, err := git.ParseUpstreamURL()
	if err != nil {
		return err
	}

	// Group commits by story ID.
	//
	// In case the commit is associated with a story, we add it to the relevant story group.
	// Otherwise the commit is marked as unassigned and added to the relevant list.
	var (
		ctxsByStoryId     = make(map[string][]*common.ReviewContext, 1)
		unassignedCommits = make([]*git.Commit, 0, 1)
	)
	for _, ctx := range ctxs {
		story := ctx.Story
		if story != nil {
			sid := story.ReadableId()
			rcs, ok := ctxsByStoryId[sid]
			if ok {
				rcs = append(rcs, ctx)
			} else {
				rcs = []*common.ReviewContext{ctx}
			}
			ctxsByStoryId[sid] = rcs
		} else {
			unassignedCommits = append(unassignedCommits, ctx.Commit)
		}
	}

	// Post the assigned commits.
	for _, ctxs := range ctxsByStoryId {
		var (
			story   = ctxs[0].Story
			commits = make([]*git.Commit, 0, len(ctxs))
		)
		for _, ctx := range ctxs {
			commits = append(commits, ctx.Commit)
		}
		if ex := postAssignedReviewRequest(config, owner, repo, story, commits, opts); ex != nil {
			errs.Log(ex)
			err = errPostReviewRequest
		}
	}

	// Post the unassigned commits.
	for _, commit := range unassignedCommits {
		if ex := postUnassignedReviewRequest(config, owner, repo, commit, opts); ex != nil {
			errs.Log(ex)
			err = errPostReviewRequest
		}
	}

	return
}
func (rnm *releaseNotesManager) PostReleaseNotes(
	releaseNotes *common.ReleaseNotes,
) (action.Action, error) {

	// Get the GitHub owner and repository from the upstream URL.
	owner, repo, err := git.ParseUpstreamURL()
	if err != nil {
		return nil, err
	}

	// Instantiate the API client.
	config, err := github.LoadConfig()
	if err != nil {
		return nil, err
	}
	client := github.NewClient(config.ApiToken())

	// Format the release notes.
	task := "Format the release notes"
	body := bytes.NewBufferString(`
## Overview ##

**PLEASE FILL IN THE RELEASE OVERVIEW**
`)
	encoder, err := notes.NewEncoder(notes.EncodingMarkdown, body)
	if err != nil {
		return nil, errs.NewError(task, err)
	}
	if err := encoder.Encode(releaseNotes, nil); err != nil {
		return nil, errs.NewError(task, err)
	}
	bodyString := body.String()

	// Create GitHub release for the given version.
	tag := releaseNotes.Version.ReleaseTagString()
	releaseTask := fmt.Sprintf("Create GitHub release for tag '%v'", tag)
	log.Run(releaseTask)
	release, _, err := client.Repositories.CreateRelease(owner, repo, &gh.RepositoryRelease{
		TagName: gh.String(tag),
		Name:    gh.String("Release " + releaseNotes.Version.BaseString()),
		Body:    &bodyString,
		Draft:   gh.Bool(true),
	})
	if err != nil {
		return nil, err
	}

	// Delete the GitHub release on rollback.
	rollback := func() error {
		log.Rollback(releaseTask)
		task := fmt.Sprintf("Delete GitHub release for tag '%v'", tag)
		_, err := client.Repositories.DeleteRelease(owner, repo, *release.ID)
		if err != nil {
			return errs.NewError(task, err)
		}
		return nil
	}

	// Open the release in the browser so that the user can fill in the details.
	task = "Open the release notes in the browser"
	if err := webbrowser.Open(*release.HTMLURL); err != nil {
		if ex := rollback(); ex != nil {
			errs.Log(ex)
		}
		return nil, errs.NewError(task, err)
	}

	// Return the rollback function.
	return action.ActionFunc(rollback), nil
}