Skip to content

Commit

Permalink
feat: add repo-include and repo-exclude options (#426)
Browse files Browse the repository at this point in the history
  • Loading branch information
josealdaco authored Nov 9, 2023
1 parent 81d60d8 commit faf0092
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 28 deletions.
64 changes: 44 additions & 20 deletions cmd/cmd-run.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ package cmd

import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"regexp"
"strings"
"syscall"

"github.com/lindell/multi-gitter/internal/git"

"github.com/lindell/multi-gitter/internal/multigitter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -62,6 +63,8 @@ Available values:
cmd.Flags().StringSliceP("labels", "", nil, "Labels to be added to any created pull request.")
cmd.Flags().StringP("author-name", "", "", "Name of the committer. If not set, the global git config setting will be used.")
cmd.Flags().StringP("author-email", "", "", "Email of the committer. If not set, the global git config setting will be used.")
cmd.Flags().StringP("repo-include", "", "", "Include repositories that match with a given Regular Expression")
cmd.Flags().StringP("repo-exclude", "", "", "Exclude repositories that match with a given Regular Expression")
configureGit(cmd)
configurePlatform(cmd)
configureRunPlatform(cmd, true)
Expand Down Expand Up @@ -98,6 +101,8 @@ func run(cmd *cobra.Command, _ []string) error {
assignees, _ := flag.GetStringSlice("assignees")
draft, _ := flag.GetBool("draft")
labels, _ := flag.GetStringSlice("labels")
repoInclude, _ := flag.GetString("repo-include")
repoExclude, _ := flag.GetString("repo-exclude")

if concurrent < 1 {
return errors.New("concurrent runs can't be less than one")
Expand Down Expand Up @@ -144,6 +149,23 @@ func run(cmd *cobra.Command, _ []string) error {
}
}

var regExIncludeRepository *regexp.Regexp
var regExExcludeRepository *regexp.Regexp
if repoInclude != "" {
repoIncludeFilterCompile, err := regexp.Compile(repoInclude)
if err != nil {
return errors.WithMessage(err, "could not parse repo-include")
}
regExIncludeRepository = repoIncludeFilterCompile
}
if repoExclude != "" {
repoExcludeFilterCompile, err := regexp.Compile(repoExclude)
if err != nil {
return errors.WithMessage(err, "could not parse repo-exclude")
}
regExExcludeRepository = repoExcludeFilterCompile
}

vc, err := getVersionController(flag, true, false)
if err != nil {
return err
Expand Down Expand Up @@ -185,25 +207,27 @@ func run(cmd *cobra.Command, _ []string) error {

VersionController: vc,

CommitMessage: commitMessage,
PullRequestTitle: prTitle,
PullRequestBody: prBody,
Reviewers: reviewers,
TeamReviewers: teamReviewers,
MaxReviewers: maxReviewers,
MaxTeamReviewers: maxTeamReviewers,
Interactive: interactive,
DryRun: dryRun,
Fork: forkMode,
ForkOwner: forkOwner,
SkipPullRequest: skipPullRequest,
SkipRepository: skipRepository,
CommitAuthor: commitAuthor,
BaseBranch: baseBranchName,
Assignees: assignees,
ConflictStrategy: conflictStrategy,
Draft: draft,
Labels: labels,
CommitMessage: commitMessage,
PullRequestTitle: prTitle,
PullRequestBody: prBody,
Reviewers: reviewers,
TeamReviewers: teamReviewers,
MaxReviewers: maxReviewers,
MaxTeamReviewers: maxTeamReviewers,
Interactive: interactive,
DryRun: dryRun,
RegExIncludeRepository: regExIncludeRepository,
RegExExcludeRepository: regExExcludeRepository,
Fork: forkMode,
ForkOwner: forkOwner,
SkipPullRequest: skipPullRequest,
SkipRepository: skipRepository,
CommitAuthor: commitAuthor,
BaseBranch: baseBranchName,
Assignees: assignees,
ConflictStrategy: conflictStrategy,
Draft: draft,
Labels: labels,

Concurrent: concurrent,

Expand Down
40 changes: 32 additions & 8 deletions internal/multigitter/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math/rand"
"os"
"os/exec"
"regexp"
"sync"
"syscall"

Expand Down Expand Up @@ -54,9 +55,11 @@ type Runner struct {
BaseBranch string // The base branch of the PR, use default branch if not set
Assignees []string

Concurrent int
SkipPullRequest bool // If set, the script will run directly on the base-branch without creating any PR
SkipRepository []string // A list of repositories that run will skip
Concurrent int
SkipPullRequest bool // If set, the script will run directly on the base-branch without creating any PR
SkipRepository []string // A list of repositories that run will skip
RegExIncludeRepository *regexp.Regexp
RegExExcludeRepository *regexp.Regexp

Fork bool // If set, create a fork and make the pull request from it
ForkOwner string // The owner of the new fork. If empty, the fork should happen on the logged in user
Expand Down Expand Up @@ -98,7 +101,7 @@ func (r *Runner) Run(ctx context.Context) error {
return errors.Wrap(err, "could not fetch repositories")
}

repos = filterRepositories(repos, r.SkipRepository)
repos = filterRepositories(repos, r.SkipRepository, r.RegExIncludeRepository, r.RegExExcludeRepository)

if len(repos) == 0 {
log.Infof("No repositories found. Please make sure the user of the token has the correct access to the repos you want to change.")
Expand Down Expand Up @@ -152,18 +155,39 @@ func (r *Runner) Run(ctx context.Context) error {
return nil
}

func filterRepositories(repos []scm.Repository, skipRepositoryNames []string) []scm.Repository {
// Determines if Repository should be excluded based on provided Regular Expression
func excludeRepositoryFilter(repoName string, regExp *regexp.Regexp) bool {
if regExp == nil {
return false
}
return regExp.MatchString(repoName)
}

// Determines if Repository should be included based on provided Regular Expression
func matchesRepositoryFilter(repoName string, regExp *regexp.Regexp) bool {
if regExp == nil {
return true
}
return regExp.MatchString(repoName)
}

func filterRepositories(repos []scm.Repository, skipRepositoryNames []string, regExIncludeRepository *regexp.Regexp,
regExExcludeRepository *regexp.Regexp) []scm.Repository {
skipReposMap := map[string]struct{}{}
for _, skipRepo := range skipRepositoryNames {
skipReposMap[skipRepo] = struct{}{}
}

filteredRepos := make([]scm.Repository, 0, len(repos))
for _, r := range repos {
if _, shouldSkip := skipReposMap[r.FullName()]; !shouldSkip {
filteredRepos = append(filteredRepos, r)
if _, shouldSkip := skipReposMap[r.FullName()]; shouldSkip {
log.Infof("Skipping %s since it is in exclusion list", r.FullName())
} else if !matchesRepositoryFilter(r.FullName(), regExIncludeRepository) {
log.Infof("Skipping %s since it does not match the inclusion regexp", r.FullName())
} else if excludeRepositoryFilter(r.FullName(), regExExcludeRepository) {
log.Infof("Skipping %s since it match the exclusion regexp", r.FullName())
} else {
log.Infof("Skipping %s", r.FullName())
filteredRepos = append(filteredRepos, r)
}
}
return filteredRepos
Expand Down
102 changes: 102 additions & 0 deletions tests/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,108 @@ func TestTable(t *testing.T) {
assert.False(t, branchExist(t, vcMock.Repositories[0].Path, "custom-branch-name"))
},
},
{
name: "repo-include regex repository filtering",
vcCreate: func(t *testing.T) *vcmock.VersionController {
return &vcmock.VersionController{
Repositories: []vcmock.Repository{
createRepo(t, "owner", "repo1", "i like apples"),
createRepo(t, "owner", "repo-2", "i like oranges"),
createRepo(t, "owner", "repo-change", "i like carrots"),
createRepo(t, "owner", "repo-3", "i like carrots"),
},
}
},
args: []string{
"run",
"--repo-search", "repo",
"--repo-include", "^owner/repo-",
"--commit-message", "chore: foo",
"--dry-run",
changerBinaryPath,
},
verify: func(t *testing.T, vcMock *vcmock.VersionController, runData runData) {
require.Len(t, vcMock.PullRequests, 0)
assert.Contains(t, runData.logOut, "Running on 3 repositories")
},
},
{
name: "repo-exclude regex repository filtering",
vcCreate: func(t *testing.T) *vcmock.VersionController {
return &vcmock.VersionController{
Repositories: []vcmock.Repository{
createRepo(t, "owner", "repo1", "i like apples"),
createRepo(t, "owner", "repo-2", "i like oranges"),
createRepo(t, "owner", "repo-change", "i like carrots"),
createRepo(t, "owner", "repo-3", "i like carrots"),
},
}
},
args: []string{
"run",
"--repo-search", "repo",
"--repo-exclude", "\\d$",
"--commit-message", "chore: foo",
"--dry-run",
changerBinaryPath,
},
verify: func(t *testing.T, vcMock *vcmock.VersionController, runData runData) {
require.Len(t, vcMock.PullRequests, 0)
assert.Contains(t, runData.logOut, "Running on 1 repositories")
},
},
{
name: "invalid repo-include regex repository filtering",
vcCreate: func(t *testing.T) *vcmock.VersionController {
return &vcmock.VersionController{
Repositories: []vcmock.Repository{
createRepo(t, "owner", "repo1", "i like apples"),
createRepo(t, "owner", "repo-2", "i like oranges"),
createRepo(t, "owner", "repo-change", "i like carrots"),
createRepo(t, "owner", "repo-3", "i like carrots"),
},
}
},
args: []string{
"run",
"--repo-search", "repo",
"--repo-include", "(abc[def$",
"--commit-message", "chore: foo",
"--dry-run",
changerBinaryPath,
},
verify: func(t *testing.T, vcMock *vcmock.VersionController, runData runData) {
require.Len(t, vcMock.PullRequests, 0)
assert.Contains(t, runData.cmdOut, "could not parse repo-include")
},
expectErr: true,
},
{
name: "invalid repo-exclude regex repository filtering",
vcCreate: func(t *testing.T) *vcmock.VersionController {
return &vcmock.VersionController{
Repositories: []vcmock.Repository{
createRepo(t, "owner", "repo1", "i like apples"),
createRepo(t, "owner", "repo-2", "i like oranges"),
createRepo(t, "owner", "repo-change", "i like carrots"),
createRepo(t, "owner", "repo-3", "i like carrots"),
},
}
},
args: []string{
"run",
"--repo-search", "repo",
"--repo-exclude", "(abc[def$",
"--commit-message", "chore: foo",
"--dry-run",
changerBinaryPath,
},
verify: func(t *testing.T, vcMock *vcmock.VersionController, runData runData) {
require.Len(t, vcMock.PullRequests, 0)
assert.Contains(t, runData.cmdOut, "could not parse repo-exclude")
},
expectErr: true,
},

{
name: "parallel",
Expand Down

0 comments on commit faf0092

Please sign in to comment.