Пример #1
0
func RunCommand(command string, args ...string) (stdout *bytes.Buffer, err error) {
	argsList := make([]string, 2, 2+len(args))
	argsList[0], argsList[1] = "--no-pager", command
	argsList = append(argsList, args...)

	task := fmt.Sprintf("Run 'git %v' with args = %#v", command, args)
	log.V(log.Debug).Log(task)
	stdout, stderr, err := shell.Run("git", argsList...)
	if err != nil {
		return nil, errs.NewErrorWithHint(task, err, stderr.String())
	}
	return stdout, nil
}
Пример #2
0
// EnsureValueFilled returns an error in case the value passed in is not set.
//
// The function checks structs and slices recursively.
func EnsureValueFilled(value interface{}, path string) error {
	logger := log.V(log.Debug)

	// Turn the interface into reflect.Value.
	var (
		v = reflect.ValueOf(value)
		t = v.Type()
	)

	logger.Log(fmt.Sprintf(`config.EnsureValueFilled: Checking "%v" ... `, path))

	// Handle pointers in a special way.
	if kind := v.Kind(); kind == reflect.Ptr || kind == reflect.Slice {
		if v.IsNil() {
			logger.NewLine("  ---> Nil")
			return &ErrKeyNotSet{path}
		}
	}

	// Decide what to do depending on the value kind.
	iv := reflect.Indirect(v)
	switch iv.Kind() {
	case reflect.Struct:
		return ensureStructFilled(iv, path)
	case reflect.Slice:
		return ensureSliceFilled(iv, path)
	}

	// In case the value is not valid, return an error.
	if !v.IsValid() {
		logger.NewLine("  ---> Invalid")
		return &ErrKeyNotSet{path}
	}

	// In case the field is set to the zero value of the given type,
	// we return an error since the field is not set.
	if reflect.DeepEqual(v.Interface(), reflect.Zero(t).Interface()) {
		logger.NewLine("  ---> Unset")
		return &ErrKeyNotSet{path}
	}

	if logger {
		logger.NewLine(fmt.Sprintf("  ---> OK (set to '%v')", v.Interface()))
	}
	return nil
}
Пример #3
0
// searchIssues can be used to query GitHub for issues matching the given filter.
// It handles pagination internally, it fetches all matching issues automatically.
func (tracker *issueTracker) searchIssues(
	queryFormat string,
	v ...interface{},
) ([]*github.Issue, error) {

	logger := log.V(log.Debug)

	// Format the query.
	query := fmt.Sprintf(queryFormat, v...)

	// Since GH API does not allow OR queries, we need to send a concurrent request
	// for every item in tracker.config.StoryLabels label list.
	ch := make(chan *searchResult, len(tracker.config.StoryLabels))
	for _, label := range tracker.config.StoryLabels {
		go func(label string) {
			// We are only interested in issues for the given repository.
			innerQuery := fmt.Sprintf(`%v type:issue repo:%v/%v label:"%v"`,
				query, tracker.config.GitHubOwner, tracker.config.GitHubRepository, label)

			task := "Search GitHub: " + innerQuery

			if logger {
				logger.Go(task)
			}

			searchOpts := &github.SearchOptions{}
			searchOpts.Page = 1
			searchOpts.PerPage = 50

			var (
				acc      []*github.Issue
				searched int
			)

			client := tracker.newClient()

			for {
				// Fetch another page.
				var (
					result *github.IssuesSearchResult
					err    error
				)
				withRequestAllocated(func() {
					result, _, err = client.Search.Issues(innerQuery, searchOpts)
				})
				if err != nil {
					ch <- &searchResult{nil, errs.NewError(task, err)}
					return
				}

				// Check the issues for exact string match.
				for i := range result.Issues {
					acc = append(acc, &result.Issues[i])
				}

				// Check whether we have reached the end or not.
				searched += len(result.Issues)
				if searched == *result.Total {
					ch <- &searchResult{acc, nil}
					return
				}

				// Check the next page in the next iteration.
				searchOpts.Page += 1
			}
		}(label)
	}

	// Collect the results.
	var issues []*github.Issue
	for i := 0; i < cap(ch); i++ {
		res := <-ch
		if err := res.err; err != nil {
			return nil, err
		}
		issues = append(issues, res.issues...)
	}

	// Make sure there are no duplicates in the list.
	return dedupeIssues(issues), nil
}
Пример #4
0
func filterBranches(storyBranches []*git.GitBranch, trunkName string) ([]*gitBranch, error) {
	// Pair the branches with commit ranges specified by trunk..story
	task := "Collected commits associated with the story branches"
	branches := make([]*gitBranch, 0, len(storyBranches))
	for _, branch := range storyBranches {
		var revRange string
		if branch.BranchName != "" {
			// Handle branches that exist locally.
			revRange = fmt.Sprintf("%v..%v", trunkName, branch.BranchName)
		} else {
			// Handle branches that exist only in the remote repository.
			// We can use trunkName here since trunk is up to date.
			revRange = fmt.Sprintf("%v..%v/%v", trunkName, branch.Remote, branch.RemoteBranchName)
		}

		commits, err := git.ShowCommitRange(revRange)
		if err != nil {
			return nil, errs.NewError(task, err)
		}
		branches = append(branches, &gitBranch{
			tip:     branch,
			commits: commits,
		})
		continue
	}

	// Collect story tags.
	task = "Collect affected story tags"
	tracker, err := modules.GetIssueTracker()
	if err != nil {
		return nil, errs.NewError(task, err)
	}

	tags := make([]string, 0, len(storyBranches))
BranchLoop:
	for _, branch := range branches {
		for _, commit := range branch.commits {
			commitTag := commit.StoryIdTag

			// Make sure the tag is not in the list already.
			for _, tag := range tags {
				if tag == commitTag {
					continue BranchLoop
				}
			}

			// Drop tags not recognized by the current issue tracker.
			_, err := tracker.StoryTagToReadableStoryId(commitTag)
			if err == nil {
				tags = append(tags, commitTag)
			}
		}
	}

	// Fetch the collected stories.
	task = "Fetch associated stories from the issue tracker"
	log.Run(task)
	stories, err := tracker.ListStoriesByTag(tags)
	if err != nil {
		return nil, errs.NewError(task, err)
	}

	// Filter the branches according to the story state.
	storyByTag := make(map[string]common.Story, len(stories))
	for i, story := range stories {
		// tags[i] corresponds to stories[i]
		tag := tags[i]
		if story != nil {
			storyByTag[tag] = story
		} else {
			log.Warn(fmt.Sprintf("Story for tag '%v' was not found in the issue tracker", tag))
		}
	}

	allowedStates := allowedStoryStates()

	// checkCommits returns whether the commits passed in are ok
	// considering the state of the stories found in these commits,
	// whether the branch containing these commits can be deleted.
	checkCommits := func(commits []*git.Commit) (common.StoryState, bool) {
		var storyFound bool
		for _, commit := range commits {
			// Skip commits with empty Story-Id tag.
			if commit.StoryIdTag == "" {
				continue
			}

			// In case the story is not found, the tag is not recognized
			// by the current issue tracker. In that case we just skip the commit.
			story, ok := storyByTag[commit.StoryIdTag]
			if !ok {
				continue
			}

			// When the story state associated with the commit is not ok,
			// we can return false here to reject the branch.
			storyState := story.State()
			if _, ok := allowedStates[storyState]; !ok {
				return storyState, false
			}

			storyFound = true
		}

		// We went through all the commits and they are fine, check passed.
		return common.StoryStateInvalid, storyFound
	}

	// Go through the branches and only return these that
	// comply with the story state requirements.
	bs := make([]*gitBranch, 0, len(branches))
	for _, branch := range branches {
		tip := branch.tip

		logger := log.V(log.Verbose)
		if logger {
			logger.Log(fmt.Sprintf("Processing branch %v", tip.CanonicalName()))
		}

		// The branch can be for sure deleted in case there are no commits
		// contained in the commit range. That means the branch is merged into trunk.
		if len(branch.commits) == 0 {
			if logger {
				logger.Log("  Include the branch (reason: merged into trunk)")
			}
			branch.reason = "merged"
			bs = append(bs, branch)
			continue
		}

		// In case the commit check passed, we append the branch.
		state, ok := checkCommits(branch.commits)
		if ok {
			if logger {
				logger.Log("  Include the branch (reason: branch check passed)")
			}
			branch.reason = "check passed"
			bs = append(bs, branch)
			continue
		}

		// Otherwise we print the skip warning.
		if logger {
			if state == common.StoryStateInvalid {
				logger.Log(
					"  Exclude the branch (reason: no story commits found on the branch)")
			} else {
				logger.Log(fmt.Sprintf(
					"  Exclude the branch (reason: story state is '%v')", state))
			}
		}
	}

	return bs, nil
}
Пример #5
0
// Log just calls LogWith(err, log.V(log.Info)).
func Log(err error) error {
	return LogWith(err, log.V(log.Info))
}
Пример #6
0
func postReviewRequestForCommit(
	ctx *common.ReviewContext,
	opts map[string]interface{},
) error {

	var (
		commit = ctx.Commit
		story  = ctx.Story
	)

	// Assert that certain field are set.
	switch {
	case commit.SHA == "":
		panic("SHA not set for the commit being posted")
	case commit.StoryIdTag == "":
		panic("story ID not set for the commit being posted")
	}

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

	// Parse the options.
	var (
		fixes  = formatOptInteger(opts["fixes"])
		update = formatOptInteger(opts["update"])
		open   bool
	)
	if _, ok := opts["open"]; ok {
		open = true
	}

	// Post the review request.
	args := []string{"post",
		"--server", config.ServerURL().String(),
		"--guess-fields", "yes",
	}

	if story != nil {
		args = append(args, "--bugs-closed", commit.StoryIdTag)
	}
	if fixes != "" {
		args = append(args, "--depends-on", fixes)
	}
	if update != "" {
		args = append(args, "--review-request-id", update)
	}
	if open {
		args = append(args, "--open")
	}
	args = append(args, commit.SHA)

	var task string
	if update != "" {
		task = "Update a Review Board review request with commit " + commit.SHA
	} else {
		task = "Create a Review Board review request for commit " + commit.SHA
	}
	log.Run(task)
	stdout, stderr, err := shell.Run("rbt", args...)
	if err != nil {
		// rbt is retarded and sometimes prints stderr to stdout.
		// That is why we return stdout when stderr is empty.
		if stderr.Len() == 0 {
			return errs.NewErrorWithHint(task, err, stdout.String())
		} else {
			return errs.NewErrorWithHint(task, err, stderr.String())
		}
	}
	logger := log.V(log.Info)
	logger.Lock()
	logger.UnsafeNewLine("")
	logger.UnsafeOk(task)
	fmt.Print(stdout)
	logger.Unlock()
	return nil
}