317 lines
8.8 KiB
Go
317 lines
8.8 KiB
Go
package merge
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"code.obermui.de/6543/GitLab_MergeDevel2Default/context"
|
|
"code.obermui.de/6543/GitLab_MergeDevel2Default/utils"
|
|
|
|
"github.com/AlecAivazis/survey/v2"
|
|
"github.com/gobwas/glob"
|
|
"github.com/urfave/cli/v2"
|
|
"github.com/xanzy/go-gitlab"
|
|
)
|
|
|
|
var (
|
|
Version = "dev"
|
|
)
|
|
|
|
type pullCache struct {
|
|
repoID int
|
|
iid int
|
|
webURL string
|
|
}
|
|
|
|
const (
|
|
perPage = 10
|
|
waitSecForMerge = 3
|
|
)
|
|
|
|
var CmdMerge = cli.Command{
|
|
Name: "merge",
|
|
Usage: "Merge develop branch into default branch",
|
|
Description: `run script for merge subcommand`,
|
|
Action: runMerge,
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "token",
|
|
Usage: "GitLab Access Token",
|
|
EnvVars: []string{"TOKEN"},
|
|
Required: false,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "url",
|
|
Usage: "GitLab Base URL",
|
|
EnvVars: []string{"BASE_URL"},
|
|
Required: false,
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "group-id",
|
|
Usage: "Group ID where repositories are stored",
|
|
EnvVars: []string{"GROUP_ID"},
|
|
Required: false,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "group-name",
|
|
Usage: "Group Name where repositories are stored, it's used if group-id is not set",
|
|
EnvVars: []string{"GROUP_NAME"},
|
|
Required: false,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "repo-ignore-pattern",
|
|
Usage: "Repo Ignore Pattern",
|
|
EnvVars: []string{"REPO_IGNORE_PATTERN"},
|
|
Required: false,
|
|
},
|
|
|
|
&cli.StringFlag{
|
|
Name: "branch-pattern",
|
|
Usage: "Merge Branches with patter",
|
|
EnvVars: []string{"BRANCH_PATTERN"},
|
|
Required: false,
|
|
},
|
|
},
|
|
}
|
|
|
|
// runMerge run script for merge subcommand
|
|
func runMerge(ctx *cli.Context) error {
|
|
values := context.LoadCache()
|
|
interactive := false
|
|
|
|
if ctx.IsSet("token") {
|
|
values.Token = ctx.String("token")
|
|
} else {
|
|
interactive = true
|
|
if err := survey.AskOne(&survey.Input{Message: "GitLab Token:", Default: values.Token}, &values.Token); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if ctx.IsSet("url") {
|
|
values.BaseURL = ctx.String("url")
|
|
} else {
|
|
interactive = true
|
|
if err := survey.AskOne(&survey.Input{Message: "GitLab Base URL:", Default: values.BaseURL}, &values.BaseURL); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
orgID := ctx.Int("group-id")
|
|
if orgID == 0 {
|
|
if ctx.IsSet("group-name") {
|
|
values.OrgName = ctx.String("group-name")
|
|
} else {
|
|
interactive = true
|
|
if err := survey.AskOne(&survey.Input{Message: "Group Name:", Default: values.OrgName}, &values.OrgName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if ctx.IsSet("repo-ignore-pattern") {
|
|
values.RepoExclude = ctx.String("repo-ignore-pattern")
|
|
} else {
|
|
interactive = true
|
|
if err := survey.AskOne(&survey.Input{Message: "Ignore Repo with patter:", Default: values.RepoExclude}, &values.RepoExclude); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if ctx.IsSet("branch-pattern") {
|
|
values.PullBaseBranch = ctx.String("branch-pattern")
|
|
} else {
|
|
interactive = true
|
|
if err := survey.AskOne(&survey.Input{Message: "Merge Branches with patter:", Default: values.PullBaseBranch}, &values.PullBaseBranch); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if interactive {
|
|
context.SaveCache(values)
|
|
}
|
|
|
|
// Compile glob regex
|
|
excludeRule, err := glob.Compile(values.RepoExclude)
|
|
if err != nil {
|
|
return fmt.Errorf("not able to compile regex '%s'", values.RepoExclude)
|
|
}
|
|
baseBranchRule, err := glob.Compile(values.PullBaseBranch)
|
|
if err != nil {
|
|
return fmt.Errorf("not able to compile regex '%s'", values.PullBaseBranch)
|
|
}
|
|
|
|
// connect to gitlab
|
|
client, err := gitlab.NewClient(values.Token, gitlab.WithBaseURL(values.BaseURL))
|
|
if err != nil {
|
|
return fmt.Errorf("Could not create Client: %v\n", err)
|
|
}
|
|
|
|
if orgID == 0 {
|
|
// discover GroupID by name
|
|
org, _, err := client.Groups.GetGroup(values.OrgName)
|
|
if err != nil {
|
|
return fmt.Errorf("Error cant get information for Organisation \"%s\": %v", values.OrgName, err)
|
|
}
|
|
fmt.Printf("Found \"%s\"\n", org.WebURL)
|
|
|
|
orgID = org.ID
|
|
}
|
|
|
|
var repoList []*gitlab.Project
|
|
var page = 1
|
|
fmt.Printf("Retrieving repository list...\n")
|
|
for {
|
|
fmt.Printf("%d", page)
|
|
repos, _, err := client.Groups.ListGroupProjects(orgID, &gitlab.ListGroupProjectsOptions{
|
|
ListOptions: gitlab.ListOptions{PerPage: perPage, Page: page},
|
|
Archived: utils.OptBool(false),
|
|
IncludeSubgroups: utils.OptBool(false),
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("Could not obtain repo list: %v\n", err)
|
|
}
|
|
|
|
repoList = append(repoList, repos...)
|
|
if len(repos) < perPage {
|
|
break
|
|
}
|
|
page++
|
|
}
|
|
fmt.Println()
|
|
|
|
var pullsToMergeCache []*pullCache
|
|
for i := range repoList {
|
|
if !excludeRule.Match(repoList[i].Name) {
|
|
// for each NOT excluded repo ...
|
|
pull, err := threatRepo(baseBranchRule, client, repoList[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pullsToMergeCache = append(pullsToMergeCache, pull)
|
|
}
|
|
}
|
|
|
|
fmt.Printf("\n\nwait 3sec for gitlab before merge created pulls\n")
|
|
time.Sleep(waitSecForMerge * time.Second)
|
|
mergePulls(client, pullsToMergeCache)
|
|
|
|
return nil
|
|
}
|
|
|
|
// threatRepo create (if needed) & merge "develop[ment]" branch in DefaultBranch
|
|
// and return pulls to merge
|
|
func threatRepo(baseBranchRule glob.Glob, client *gitlab.Client, repo *gitlab.Project) (*pullCache, error) {
|
|
fmt.Printf("Handle Repository '%s'\n", repo.Name)
|
|
|
|
var branchList []*gitlab.Branch
|
|
var page = 1
|
|
for {
|
|
bL, _, err := client.Branches.ListBranches(repo.ID, &gitlab.ListBranchesOptions{
|
|
ListOptions: gitlab.ListOptions{
|
|
Page: page,
|
|
PerPage: perPage,
|
|
},
|
|
})
|
|
if err != nil {
|
|
utils.Error(10, "Could not obtain branch list from '%s': %v\n", repo.Name, err)
|
|
}
|
|
|
|
branchList = append(branchList, bL...)
|
|
if len(bL) < perPage {
|
|
break
|
|
}
|
|
page++
|
|
}
|
|
|
|
fmt.Printf(" found %d branches:", len(branchList))
|
|
fmt.Printf(" ")
|
|
for _, b := range branchList {
|
|
fmt.Printf("'%s' ", b.Name)
|
|
}
|
|
fmt.Println()
|
|
|
|
for _, baseBranch := range branchList {
|
|
if baseBranchRule.Match(baseBranch.Name) && !baseBranch.Default {
|
|
fmt.Printf(" found branch '%s'\n", baseBranch.Name)
|
|
|
|
// check if source is ahead target branch
|
|
diff, _, err := client.Repositories.Compare(repo.ID, &gitlab.CompareOptions{
|
|
From: &repo.DefaultBranch,
|
|
To: &baseBranch.Name,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not compare '%s'...'%s' at '%s': %v", repo.DefaultBranch, baseBranch.Name, repo.Name, err)
|
|
}
|
|
|
|
if len(diff.Commits) == 0 {
|
|
fmt.Printf(" branch '%s' is not ahead of '%s', skiping\n", baseBranch.Name, repo.DefaultBranch)
|
|
return nil, nil
|
|
}
|
|
|
|
// check if pull already exist
|
|
pullList, _, err := client.MergeRequests.ListProjectMergeRequests(repo.ID, &gitlab.ListProjectMergeRequestsOptions{
|
|
SourceBranch: &baseBranch.Name,
|
|
TargetBranch: &repo.DefaultBranch,
|
|
State: utils.OptString("opened"),
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not obtain merge request list from '%s': %v", repo.Name, err)
|
|
}
|
|
|
|
// if not create one
|
|
if len(pullList) == 0 {
|
|
fmt.Printf(" no existing pull for %s, creating one ...\n", baseBranch.Name)
|
|
pull, _, err := client.MergeRequests.CreateMergeRequest(repo.ID, &gitlab.CreateMergeRequestOptions{
|
|
Title: utils.OptString(fmt.Sprintf("%s <- %s", repo.DefaultBranch, baseBranch.Name)),
|
|
Description: utils.OptString("Auto created by https://code.obermui.de/6543/GitLab_MergeDevel2Default"),
|
|
SourceBranch: &baseBranch.Name,
|
|
TargetBranch: &repo.DefaultBranch,
|
|
AllowCollaboration: utils.OptBool(true),
|
|
})
|
|
if err != nil {
|
|
fmt.Printf(" could not create merge request '%s <- %s', skiping '%s'\n", repo.DefaultBranch, baseBranch.Name, repo.Name)
|
|
return nil, nil
|
|
}
|
|
pullList = []*gitlab.MergeRequest{pull}
|
|
} else {
|
|
// check if changes exist for pull
|
|
changes, _, err := client.MergeRequests.GetMergeRequestChanges(repo.ID, pullList[0].IID, nil)
|
|
if err != nil {
|
|
fmt.Printf(" could not obtain changes of merge request '%s', skiping '%s'\n", pullList[0].WebURL, repo.Name)
|
|
return nil, nil
|
|
}
|
|
|
|
if len(changes.Changes) == 0 {
|
|
fmt.Printf(" pull '%s' does not conatin changes, skiping\n", changes.WebURL)
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
// since we had a match go to next repo ... and return pull
|
|
return &pullCache{
|
|
repoID: repo.ID,
|
|
iid: pullList[0].IID,
|
|
webURL: pullList[0].WebURL,
|
|
}, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func mergePulls(c *gitlab.Client, pulls []*pullCache) {
|
|
for i := range pulls {
|
|
if pull, _, err := c.MergeRequests.AcceptMergeRequest(pulls[i].repoID, pulls[i].iid, nil); err != nil {
|
|
fmt.Printf(" pull '%s' can not be merged: %v\n", pulls[i].webURL, err)
|
|
} else {
|
|
fmt.Printf(" pull '%s' got merged successfully\n", pull.WebURL)
|
|
|
|
if err, _ := c.Branches.DeleteBranch(pulls[i].repoID, pull.SourceBranch); err != nil {
|
|
fmt.Printf(" branch '%s' can not be deleted: %v\n", pull.SourceBranch, err)
|
|
} else {
|
|
fmt.Printf(" branch '%s' successfully deleted\n", pull.SourceBranch)
|
|
}
|
|
}
|
|
}
|
|
}
|