Skip to content

Commit

Permalink
feat: added fork mode (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
lindell authored Jun 14, 2021
1 parent 776b929 commit f9e7827
Show file tree
Hide file tree
Showing 15 changed files with 572 additions and 206 deletions.
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ issues:
- errcheck
- dupl
- gosec
- unparam
- path: cmd/ # The cmd package contains a lot of descriptions that must be on a single long line
linters:
- lll
Expand Down
3 changes: 2 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ func createGithubClient(flag *flag.FlagSet, verifyFlags bool) (multigitter.Versi
orgs, _ := flag.GetStringSlice("org")
users, _ := flag.GetStringSlice("user")
repos, _ := flag.GetStringSlice("repo")
forkMode, _ := flag.GetBool("fork")

if verifyFlags && len(orgs) == 0 && len(users) == 0 && len(repos) == 0 {
return nil, errors.New("no organization, user or repo set")
Expand Down Expand Up @@ -254,7 +255,7 @@ func createGithubClient(flag *flag.FlagSet, verifyFlags bool) (multigitter.Versi
Organizations: orgs,
Users: users,
Repositories: repoRefs,
}, mergeTypes)
}, mergeTypes, forkMode)
if err != nil {
return nil, err
}
Expand Down
10 changes: 10 additions & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ func RunCmd() *cobra.Command {
cmd.Flags().IntP("fetch-depth", "f", 1, "Limit fetching to the specified number of commits. Set to 0 for no limit")
cmd.Flags().BoolP("skip-pr", "", false, "Skip pull request and directly push to the branch")
cmd.Flags().BoolP("dry-run", "d", false, "Run without pushing changes or creating pull requests")
cmd.Flags().BoolP("fork", "", false, "Fork the repository instead of creating a new branch on the same owner")
cmd.Flags().StringP("fork-owner", "", "", "If set, make the fork to defined one. Default behavior is for the fork to be on the logged in user.")
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.")
configurePlatform(cmd)
Expand All @@ -68,6 +70,8 @@ func run(cmd *cobra.Command, args []string) error {
concurrent, _ := flag.GetInt("concurrent")
skipPullRequest, _ := flag.GetBool("skip-pr")
dryRun, _ := flag.GetBool("dry-run")
forkMode, _ := flag.GetBool("fork")
forkOwner, _ := flag.GetString("fork-owner")
authorName, _ := flag.GetString("author-name")
authorEmail, _ := flag.GetString("author-email")
strOutput, _ := flag.GetString("output")
Expand Down Expand Up @@ -104,6 +108,10 @@ func run(cmd *cobra.Command, args []string) error {
}
}

if skipPullRequest && forkMode {
return errors.New("--fork and --skip-pr can't be used at the same time")
}

// Parse commit author data
var commitAuthor *domain.CommitAuthor
if authorName != "" || authorEmail != "" {
Expand Down Expand Up @@ -170,6 +178,8 @@ func run(cmd *cobra.Command, args []string) error {
Reviewers: reviewers,
MaxReviewers: maxReviewers,
DryRun: dryRun,
Fork: forkMode,
ForkOwner: forkOwner,
SkipPullRequest: skipPullRequest,
CommitAuthor: commitAuthor,
BaseBranch: baseBranchName,
Expand Down
50 changes: 27 additions & 23 deletions internal/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package git

import (
"bytes"
"fmt"
"time"

"github.com/go-git/go-git/v5/config"
Expand Down Expand Up @@ -39,20 +38,6 @@ func (g *Git) Clone(baseName, headName string) error {
return errors.Wrap(err, "could not clone from the remote")
}

if headName != "" {
err = r.Fetch(&git.FetchOptions{
RefSpecs: []config.RefSpec{
config.RefSpec(fmt.Sprintf("refs/heads/%s:refs/remotes/origin/%s", headName, headName)),
},
Depth: g.FetchDepth,
})
if err != nil {
if _, ok := err.(git.NoMatchingRefSpecError); !ok {
return err
}
}
}

g.repo = r

return nil
Expand Down Expand Up @@ -184,17 +169,36 @@ func (g *Git) logDiff(aHash, bHash plumbing.Hash) error {
}

// BranchExist checks if the new branch exists
func (g *Git) BranchExist(branchName string) (bool, error) {
_, err := g.repo.Reference(plumbing.ReferenceName(fmt.Sprintf("refs/remotes/origin/%s", branchName)), false)
if err == plumbing.ErrReferenceNotFound {
return false, nil
} else if err != nil {
func (g *Git) BranchExist(remoteName, branchName string) (bool, error) {
remote, err := g.repo.Remote(remoteName)
if err != nil {
return false, err
}

refs, err := remote.List(&git.ListOptions{})
if err != nil {
return false, err
}
return true, nil
for _, r := range refs {
if r.Name().Short() == branchName {
return true, nil
}
}
return false, nil
}

// Push the committed changes to the remote
func (g *Git) Push() error {
return g.repo.Push(&git.PushOptions{})
func (g *Git) Push(remoteName string) error {
return g.repo.Push(&git.PushOptions{
RemoteName: remoteName,
})
}

// AddRemote adds a new remote
func (g *Git) AddRemote(name, url string) error {
_, err := g.repo.CreateRemote(&config.RemoteConfig{
Name: name,
URLs: []string{url},
})
return err
}
44 changes: 34 additions & 10 deletions internal/multigitter/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ import (
// VersionController fetches repositories
type VersionController interface {
GetRepositories(ctx context.Context) ([]domain.Repository, error)
CreatePullRequest(ctx context.Context, repo domain.Repository, newPR domain.NewPullRequest) (domain.PullRequest, error)
CreatePullRequest(ctx context.Context, repo domain.Repository, prRepo domain.Repository, newPR domain.NewPullRequest) (domain.PullRequest, error)
GetPullRequests(ctx context.Context, branchName string) ([]domain.PullRequest, error)
MergePullRequest(ctx context.Context, pr domain.PullRequest) error
ClosePullRequest(ctx context.Context, pr domain.PullRequest) error
ForkRepository(ctx context.Context, repo domain.Repository, newOwner string) (domain.Repository, error)
}

// Runner contains fields to be able to do the run
Expand All @@ -51,6 +52,9 @@ type Runner struct {
FetchDepth int // Limit fetching to the specified number of commits. Set to 0 for no limit
Concurrent int
SkipPullRequest bool // If set, the script will run directly on the base-branch without creating any PR

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
}

var errAborted = errors.New("run was never started because of aborted execution")
Expand Down Expand Up @@ -89,6 +93,7 @@ func (r Runner) Run(ctx context.Context) error {

defer func() {
if r := recover(); r != nil {
log.Error(r)
rc.AddError(errors.New("run paniced"), repos[i])
}
}()
Expand Down Expand Up @@ -169,13 +174,6 @@ func (r Runner) runSingleRepo(ctx context.Context, repo domain.Repository) (doma

// Change the branch to the feature branch
if !r.SkipPullRequest {
featureBranchExist, err := sourceController.BranchExist(r.FeatureBranch)
if err != nil {
return nil, errors.Wrap(err, "could not verify if branch already exist")
} else if featureBranchExist {
return nil, domain.BranchExistError
}

err = sourceController.ChangeBranch(r.FeatureBranch)
if err != nil {
return nil, err
Expand Down Expand Up @@ -218,7 +216,33 @@ func (r Runner) runSingleRepo(ctx context.Context, repo domain.Repository) (doma
}, nil
}

err = sourceController.Push()
remoteName := "origin"
var prRepo domain.Repository = repo
if r.Fork {
log.Info("Forking repository")

prRepo, err = r.VersionController.ForkRepository(ctx, repo, r.ForkOwner)
if err != nil {
return nil, errors.Wrap(err, "could not fork repository")
}

err = sourceController.AddRemote("fork", prRepo.URL(r.Token))
if err != nil {
return nil, err
}
remoteName = "fork"
}

if !r.SkipPullRequest {
featureBranchExist, err := sourceController.BranchExist(remoteName, r.FeatureBranch)
if err != nil {
return nil, errors.Wrap(err, "could not verify if branch already exist")
} else if featureBranchExist {
return nil, domain.BranchExistError
}
}

err = sourceController.Push(remoteName)
if err != nil {
return nil, errors.Wrap(err, "could not push changes")
}
Expand All @@ -228,7 +252,7 @@ func (r Runner) runSingleRepo(ctx context.Context, repo domain.Repository) (doma
}

log.Info("Change done, creating pull request")
pr, err := r.VersionController.CreatePullRequest(ctx, repo, domain.NewPullRequest{
pr, err := r.VersionController.CreatePullRequest(ctx, repo, prRepo, domain.NewPullRequest{
Title: r.PullRequestTitle,
Body: r.PullRequestBody,
Head: r.FeatureBranch,
Expand Down
4 changes: 3 additions & 1 deletion internal/scm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ type VersionController interface {
// Should get repositories based on the scm configuration
GetRepositories(ctx context.Context) ([]domain.Repository, error)
// Creates a pull request. The repo parameter will always originate from the same package
CreatePullRequest(ctx context.Context, repo domain.Repository, newPR domain.NewPullRequest) (domain.PullRequest, error)
CreatePullRequest(ctx context.Context, repo domain.Repository, prRepo domain.Repository, newPR domain.NewPullRequest) (domain.PullRequest, error)
// Gets the latest pull requests from repositories based on the scm configuration
GetPullRequests(ctx context.Context, branchName string) ([]domain.PullRequest, error)
// Merges a pull request, the pr parameter will always originate from the same package
MergePullRequest(ctx context.Context, pr domain.PullRequest) error
// Close a pull request, the pr parameter will always originate from the same package
ClosePullRequest(ctx context.Context, pr domain.PullRequest) error
// ForkRepository forks a repository. If newOwner is set, use it, otherwise fork to the current user
ForkRepository(ctx context.Context, repo domain.Repository, newOwner string) (domain.Repository, error)
}
```

Expand Down
Loading

0 comments on commit f9e7827

Please sign in to comment.