package main import ( "fmt" "os" "github.com/AlecAivazis/survey/v2" "github.com/gobwas/glob" "github.com/xanzy/go-gitlab" ) var ( Version = "dev" ) func main() { // default values var token string = "XXXXXXXXXXXXXXXXXX" var baseURL string = "https://inf-git.fh-rosenheim.de" var orgName string = "vv-inf-sose21" var repoExclude string = "exclude*" var pullBaseBranch string = "devel*" // ask for infos if err := survey.AskOne(&survey.Input{Message: "GitLab Base URL:", Default: baseURL}, &baseURL); err != nil { os.Exit(1) } if err := survey.AskOne(&survey.Input{Message: "GitLab Token:", Default: token}, &token); err != nil { os.Exit(1) } if err := survey.AskOne(&survey.Input{Message: "Group Name:", Default: orgName}, &orgName); err != nil { os.Exit(1) } if err := survey.AskOne(&survey.Input{Message: "Ignore Repo with patter:", Default: repoExclude}, &repoExclude); err != nil { os.Exit(1) } if err := survey.AskOne(&survey.Input{Message: "Merge Branches with patter:", Default: pullBaseBranch}, &pullBaseBranch); err != nil { os.Exit(1) } // Compile glob regex excludeRule, err := glob.Compile(repoExclude) if err != nil { error(2, "not able to compile regex '%s'", repoExclude) } baseBranchRule, err := glob.Compile(pullBaseBranch) if err != nil { error(3, "not able to compile regex '%s'", pullBaseBranch) } // connect to gitlab client, err := gitlab.NewClient(token, gitlab.WithBaseURL(baseURL)) if err != nil { error(4, "Could not create Client: %v\n", err) } // discover GroupID by name org, _, err := client.Groups.GetGroup(orgName) if err != nil { error(5, "Error cant get information for Organisation \"%s\": %v", orgName, err) } fmt.Printf("Found \"%s\"\n", org.WebURL) var repoList []*gitlab.Project var page = 1 for { fmt.Printf("Get Repo List Page %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++ } for i := range repoList { if !excludeRule.Match(repoList[i].Name) { // for each NOT excluded repo ... threatRepo(baseBranchRule, client, repoList[i]) } } } // threatRepo create (if needed) & merge "develop[ment]" branch in DefaultBranch func threatRepo(baseBranchRule glob.Glob, client *gitlab.Client, repo *gitlab.Project) { fmt.Printf("Handle Repository '%s'\n", repo.Name) // TODO: handle pagination branchList, _, err := client.Branches.ListBranches(repo.ID, nil) if err != nil { error(10, "Could not obtain branch list from '%s': %v\n", repo.Name, err) } fmt.Printf(" found %d branches\n", len(branchList)) 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 } // 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)), // TODO: better title (required) 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 } 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 } if len(changes.Changes) == 0 { fmt.Printf(" pull '%s' does not conatin changes, skiping\n", changes.WebURL) return } } // TODO: check and wait for pull to get ready // merge pull if pull, _, err := client.MergeRequests.AcceptMergeRequest(repo.ID, pullList[0].IID, nil); err != nil { fmt.Printf(" pull '%s' can not be merged: %v\n", pullList[0].WebURL, err) } else { fmt.Printf(" pull '%s' got merged successfully\n", pull.WebURL) if err, _ := client.Branches.DeleteBranch(repo.ID, baseBranch.Name); err != nil { fmt.Printf(" branch '%s' can not be deleted: %v\n", baseBranch.Name, err) } else { fmt.Printf(" branch '%s' successfully deleted\n", baseBranch.Name) } } // since we had a match go to next repo ... return } } } // 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) }