Refactor from script to cli tool #9
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
/vendor
|
/vendor
|
||||||
.idea/
|
.idea/
|
||||||
.cache.json
|
.cache.json
|
||||||
|
GitLab_MergeDevel2Default
|
||||||
|
1
go.mod
1
go.mod
@ -5,5 +5,6 @@ go 1.16
|
|||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.2.12
|
github.com/AlecAivazis/survey/v2 v2.2.12
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
github.com/xanzy/go-gitlab v0.48.0
|
github.com/xanzy/go-gitlab v0.48.0
|
||||||
)
|
)
|
||||||
|
10
go.sum
10
go.sum
@ -1,7 +1,10 @@
|
|||||||
github.com/AlecAivazis/survey/v2 v2.2.12 h1:5a07y93zA6SZ09gOa9wLVLznF5zTJMQ+pJ3cZK4IuO8=
|
github.com/AlecAivazis/survey/v2 v2.2.12 h1:5a07y93zA6SZ09gOa9wLVLznF5zTJMQ+pJ3cZK4IuO8=
|
||||||
github.com/AlecAivazis/survey/v2 v2.2.12/go.mod h1:6d4saEvBsfSHXeN1a5OA5m2+HJ2LuVokllnC77pAIKI=
|
github.com/AlecAivazis/survey/v2 v2.2.12/go.mod h1:6d4saEvBsfSHXeN1a5OA5m2+HJ2LuVokllnC77pAIKI=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -31,11 +34,17 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1f
|
|||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||||
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/xanzy/go-gitlab v0.48.0 h1:RP9r4pMDIwE2fbtc+QYiC1euDsPGHcAjPkhje4X3QPU=
|
github.com/xanzy/go-gitlab v0.48.0 h1:RP9r4pMDIwE2fbtc+QYiC1euDsPGHcAjPkhje4X3QPU=
|
||||||
github.com/xanzy/go-gitlab v0.48.0/go.mod h1:UW8JJbyBbqtOyBYNHRo261IRdHUFJr2m0y0z1xUiu+E=
|
github.com/xanzy/go-gitlab v0.48.0/go.mod h1:UW8JJbyBbqtOyBYNHRo261IRdHUFJr2m0y0z1xUiu+E=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
@ -65,3 +74,4 @@ google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
277
main.go
277
main.go
@ -1,281 +1,32 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"code.obermui.de/6543/GitLab_MergeDevel2Default/merge"
|
||||||
"github.com/gobwas/glob"
|
"code.obermui.de/6543/GitLab_MergeDevel2Default/utils"
|
||||||
"github.com/xanzy/go-gitlab"
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version = "dev"
|
Version = "dev"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Cache struct {
|
|
||||||
token string `json:"token"`
|
|
||||||
baseURL string `json:"baseURL"`
|
|
||||||
orgName string `json:"orgName"`
|
|
||||||
repoExclude string `json:"repoExclude"`
|
|
||||||
pullBaseBranch string `json:"pullBaseBranch"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type pullCache struct {
|
|
||||||
repoID int
|
|
||||||
iid int
|
|
||||||
webURL string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
perPage = 10
|
|
||||||
waitSecForMerge = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
func loadCache() (cache *Cache) {
|
|
||||||
// default values
|
|
||||||
cache = &Cache{
|
|
||||||
token: "XXXXXXXXXXXXXXXXXX",
|
|
||||||
baseURL: "https://gitlab.com",
|
|
||||||
orgName: "organisation",
|
|
||||||
repoExclude: "exclude*",
|
|
||||||
pullBaseBranch: "devel*",
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(".cache.json")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var c Cache
|
|
||||||
if err := json.Unmarshal(data, &c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveCache(cache *Cache) {
|
|
||||||
data, err := json.Marshal(cache)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("ERROR: marshal cache: %v\n", err)
|
|
||||||
}
|
|
||||||
err = ioutil.WriteFile(".cache.json", data, 0600)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("ERROR: save cache: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
values := loadCache()
|
|
||||||
|
|
||||||
// ask for infos
|
app := cli.NewApp()
|
||||||
if err := survey.AskOne(&survey.Input{Message: "GitLab Base URL:", Default: values.baseURL}, &values.baseURL); err != nil {
|
app.Name = "vv-tool"
|
||||||
os.Exit(1)
|
app.Usage = "VV command line tool to interact with Gitlab"
|
||||||
}
|
app.Description = "cli tool for VV to manage things"
|
||||||
if err := survey.AskOne(&survey.Input{Message: "GitLab Token:", Default: values.token}, &values.token); err != nil {
|
app.Version = Version
|
||||||
os.Exit(1)
|
app.Commands = []*cli.Command{
|
||||||
}
|
&merge.CmdMerge,
|
||||||
if err := survey.AskOne(&survey.Input{Message: "Group Name:", Default: values.orgName}, &values.orgName); err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if err := survey.AskOne(&survey.Input{Message: "Ignore Repo with patter:", Default: values.repoExclude}, &values.repoExclude); err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if err := survey.AskOne(&survey.Input{Message: "Merge Branches with patter:", Default: values.pullBaseBranch}, &values.pullBaseBranch); err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveCache(values)
|
app.EnableBashCompletion = true
|
||||||
|
err := app.Run(os.Args)
|
||||||
// Compile glob regex
|
|
||||||
excludeRule, err := glob.Compile(values.repoExclude)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
error(2, "not able to compile regex '%s'", values.repoExclude)
|
utils.Error(1, "Error: %v\n", err)
|
||||||
}
|
|
||||||
baseBranchRule, err := glob.Compile(values.pullBaseBranch)
|
|
||||||
if err != nil {
|
|
||||||
error(3, "not able to compile regex '%s'", values.pullBaseBranch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect to gitlab
|
|
||||||
client, err := gitlab.NewClient(values.token, gitlab.WithBaseURL(values.baseURL))
|
|
||||||
if err != nil {
|
|
||||||
error(4, "Could not create Client: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// discover GroupID by name
|
|
||||||
org, _, err := client.Groups.GetGroup(values.orgName)
|
|
||||||
if err != nil {
|
|
||||||
error(5, "Error cant get information for Organisation \"%s\": %v", values.orgName, err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Found \"%s\"\n", org.WebURL)
|
|
||||||
|
|
||||||
var repoList []*gitlab.Project
|
|
||||||
var page = 1
|
|
||||||
fmt.Printf("Retrieving repository list...\n")
|
|
||||||
for {
|
|
||||||
fmt.Printf("%d", page)
|
|
||||||
repos, _, err := client.Groups.ListGroupProjects(org.ID, &gitlab.ListGroupProjectsOptions{
|
|
||||||
ListOptions: gitlab.ListOptions{PerPage: 10, Page: page},
|
|
||||||
Archived: optBool(false),
|
|
||||||
IncludeSubgroups: optBool(false),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
error(5, "Could not obtain repo list: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
repoList = append(repoList, repos...)
|
|
||||||
if len(repos) < 10 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
page++
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
var pullsToMergeCache []*pullCache
|
|
||||||
for i := range repoList {
|
|
||||||
if !excludeRule.Match(repoList[i].Name) {
|
|
||||||
// for each NOT excluded repo ...
|
|
||||||
pullsToMergeCache = append(pullsToMergeCache,
|
|
||||||
threatRepo(baseBranchRule, client, repoList[i]),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n\nwait 3sec for gitlab before merge created pulls\n")
|
|
||||||
time.Sleep(waitSecForMerge * time.Second)
|
|
||||||
mergePulls(client, pullsToMergeCache)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
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 {
|
|
||||||
error(10, "Could not obtain branch list from '%s': %v\n", repo.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
branchList = append(branchList, bL...)
|
|
||||||
if len(bL) < 10 {
|
|
||||||
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 {
|
|
||||||
error(10, "Could not compare '%s'...'%s' at '%s': %v\n", 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if pull already exist
|
|
||||||
pullList, _, err := client.MergeRequests.ListProjectMergeRequests(repo.ID, &gitlab.ListProjectMergeRequestsOptions{
|
|
||||||
SourceBranch: &baseBranch.Name,
|
|
||||||
TargetBranch: &repo.DefaultBranch,
|
|
||||||
State: optString("opened"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
error(11, "Could not obtain merge request list from '%s': %v\n", 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: optString(fmt.Sprintf("%s <- %s", repo.DefaultBranch, baseBranch.Name)),
|
|
||||||
Description: optString("Auto created by https://code.obermui.de/6543/GitLab_MergeDevel2Default"),
|
|
||||||
SourceBranch: &baseBranch.Name,
|
|
||||||
TargetBranch: &repo.DefaultBranch,
|
|
||||||
AllowCollaboration: 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
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(changes.Changes) == 0 {
|
|
||||||
fmt.Printf(" pull '%s' does not conatin changes, skiping\n", changes.WebURL)
|
|
||||||
return 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// optBool return ref of bool - dont ask it is go
|
|
||||||
func optBool(val bool) *bool {
|
|
||||||
return &val
|
|
||||||
}
|
|
||||||
|
|
||||||
// optString return ref of string - dont ask it is go
|
|
||||||
func optString(val string) *string {
|
|
||||||
return &val
|
|
||||||
}
|
|
||||||
|
|
||||||
func error(id int, format string, a ...interface{}) {
|
|
||||||
fmt.Printf(format, a...)
|
|
||||||
os.Exit(id)
|
|
||||||
}
|
|
||||||
|
280
merge/merge.go
Normal file
280
merge/merge.go
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
package merge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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 Cache struct {
|
||||||
|
token string `json:"token"`
|
||||||
|
baseURL string `json:"baseURL"`
|
||||||
|
orgName string `json:"orgName"`
|
||||||
|
repoExclude string `json:"repoExclude"`
|
||||||
|
pullBaseBranch string `json:"pullBaseBranch"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pullCache struct {
|
||||||
|
repoID int
|
||||||
|
iid int
|
||||||
|
webURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
perPage = 10
|
||||||
|
waitSecForMerge = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadCache() (cache *Cache) {
|
||||||
|
// default values
|
||||||
|
cache = &Cache{
|
||||||
|
token: "XXXXXXXXXXXXXXXXXX",
|
||||||
|
baseURL: "https://gitlab.com",
|
||||||
|
orgName: "organisation",
|
||||||
|
repoExclude: "exclude*",
|
||||||
|
pullBaseBranch: "devel*",
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(".cache.json")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var c Cache
|
||||||
|
if err := json.Unmarshal(data, &c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveCache(cache *Cache) {
|
||||||
|
data, err := json.Marshal(cache)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ERROR: marshal cache: %v\n", err)
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(".cache.json", data, 0600)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ERROR: save cache: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var CmdMerge = cli.Command{
|
||||||
|
Name: "merge",
|
||||||
|
Usage: "run script for merge subcommand",
|
||||||
|
Description: `run script for merge subcommand`,
|
||||||
|
Action: runMerge,
|
||||||
|
}
|
||||||
|
|
||||||
|
// runMerge run script for merge subcommand
|
||||||
|
func runMerge(ctx *cli.Context) error {
|
||||||
|
values := loadCache()
|
||||||
|
|
||||||
|
// ask for infos
|
||||||
|
if err := survey.AskOne(&survey.Input{Message: "GitLab Base URL:", Default: values.baseURL}, &values.baseURL); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := survey.AskOne(&survey.Input{Message: "GitLab Token:", Default: values.token}, &values.token); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := survey.AskOne(&survey.Input{Message: "Group Name:", Default: values.orgName}, &values.orgName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := survey.AskOne(&survey.Input{Message: "Ignore Repo with patter:", Default: values.repoExclude}, &values.repoExclude); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := survey.AskOne(&survey.Input{Message: "Merge Branches with patter:", Default: values.pullBaseBranch}, &values.pullBaseBranch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
var repoList []*gitlab.Project
|
||||||
|
var page = 1
|
||||||
|
fmt.Printf("Retrieving repository list...\n")
|
||||||
|
for {
|
||||||
|
fmt.Printf("%d", page)
|
||||||
|
repos, _, err := client.Groups.ListGroupProjects(org.ID, &gitlab.ListGroupProjectsOptions{
|
||||||
|
ListOptions: gitlab.ListOptions{PerPage: 10, 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) < 10 {
|
||||||
|
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) < 10 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
utils/utils.go
Normal file
22
utils/utils.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OptBool return ref of bool - dont ask it is go
|
||||||
|
func OptBool(val bool) *bool {
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptString return ref of string - dont ask it is go
|
||||||
|
func OptString(val string) *string {
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error format the error and exec the program with given status
|
||||||
|
func Error(status int, format string, a ...interface{}) {
|
||||||
|
fmt.Printf(format, a...)
|
||||||
|
os.Exit(status)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user