diff --git a/main.go b/main.go index 7044c29..f983f59 100644 --- a/main.go +++ b/main.go @@ -1,268 +1,13 @@ package main import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "time" - - "code.obermui.de/6543/GitLab_MergeDevel2Default/utils" - - "github.com/AlecAivazis/survey/v2" - "github.com/gobwas/glob" - "github.com/xanzy/go-gitlab" + "code.obermui.de/6543/GitLab_MergeDevel2Default/merge" ) 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) - } -} - func main() { - values := loadCache() - - // ask for infos - if err := survey.AskOne(&survey.Input{Message: "GitLab Base URL:", Default: values.baseURL}, &values.baseURL); err != nil { - os.Exit(1) - } - if err := survey.AskOne(&survey.Input{Message: "GitLab Token:", Default: values.token}, &values.token); err != nil { - os.Exit(1) - } - 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) - - // Compile glob regex - excludeRule, err := glob.Compile(values.repoExclude) - if err != nil { - utils.Error(2, "not able to compile regex '%s'", values.repoExclude) - } - baseBranchRule, err := glob.Compile(values.pullBaseBranch) - if err != nil { - utils.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 { - utils.Error(4, "Could not create Client: %v\n", err) - } - - // discover GroupID by name - org, _, err := client.Groups.GetGroup(values.orgName) - if err != nil { - utils.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: utils.OptBool(false), - IncludeSubgroups: utils.OptBool(false), - }) - if err != nil { - utils.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 { - 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 { - utils.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: utils.OptString("opened"), - }) - if err != nil { - utils.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: 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 - } - 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) - } - } - } + merge.RunMerge() } diff --git a/merge/merge.go b/merge/merge.go index 74a7aad..a1fbc91 100644 --- a/merge/merge.go +++ b/merge/merge.go @@ -1,2 +1,269 @@ package merge +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "time" + + "code.obermui.de/6543/GitLab_MergeDevel2Default/utils" + + "github.com/AlecAivazis/survey/v2" + "github.com/gobwas/glob" + "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) + } +} + +// RunMerge run script for merge subcommand +func RunMerge() { + values := loadCache() + + // ask for infos + if err := survey.AskOne(&survey.Input{Message: "GitLab Base URL:", Default: values.baseURL}, &values.baseURL); err != nil { + os.Exit(1) + } + if err := survey.AskOne(&survey.Input{Message: "GitLab Token:", Default: values.token}, &values.token); err != nil { + os.Exit(1) + } + 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) + + // Compile glob regex + excludeRule, err := glob.Compile(values.repoExclude) + if err != nil { + utils.Error(2, "not able to compile regex '%s'", values.repoExclude) + } + baseBranchRule, err := glob.Compile(values.pullBaseBranch) + if err != nil { + utils.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 { + utils.Error(4, "Could not create Client: %v\n", err) + } + + // discover GroupID by name + org, _, err := client.Groups.GetGroup(values.orgName) + if err != nil { + utils.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: utils.OptBool(false), + IncludeSubgroups: utils.OptBool(false), + }) + if err != nil { + utils.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 { + 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 { + utils.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: utils.OptString("opened"), + }) + if err != nil { + utils.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: 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 + } + 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) + } + } + } +}