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) } } } }