package main import ( "errors" "fmt" "net/url" "path" "gopkg.in/yaml.v3" "github.com/go-git/go-git/v5" "github.com/xanzy/go-gitlab" ) type gitlabContext struct { rootPath string repos *git.Repository apiUrl url.URL group string name string token string client *gitlab.Client project *gitlab.Project } func getGitlabContext() *gitlabContext { context := &gitlabContext{} workingPath, err := getGitDirectory() if err != nil { printErrorAndExit(2, "Failed to get git directory", err.Error()) } context.rootPath = workingPath repos, err := git.PlainOpen(workingPath) if err != nil { printErrorAndExit(2, "Failed to open repository", err.Error()) } context.repos = repos if err != nil { printErrorAndExit(2, "Failed to get remotes", err.Error()) } remotes, err := repos.Remotes() if err != nil { printErrorAndExit(2, "Failed to get remotes", err.Error()) } remote := remotes[0] if remote == nil { printErrorAndExit(2, "Failed to get remote") } urls := remote.Config().URLs if len(urls) == 0 { printErrorAndExit(2, "Failed to get remote url") } u, err := url.Parse(urls[0]) if err != nil { printErrorAndExit(2, "Failed to parse remote url", err.Error()) } context.apiUrl = *u context.apiUrl.Path = "" //path.Join("api", "v4") p := u.Path if p == "" { printErrorAndExit(2, "Failed to get remote path") } s := p[1 : len(p)-4] context.name = path.Base(s) context.group = path.Dir(s) options := gitlab.WithBaseURL(context.apiUrl.String()) context.token = config.Gitlab.Token c, err := gitlab.NewClient(config.Gitlab.Token, options) if err != nil { printErrorAndExit(2, "Failed to create client", err.Error()) } context.client = c return context } func searchProject(context *gitlabContext) { // options := gitlab.ListProjectsOptions{ Search: gitlab.String(context.group + "/" + context.name), SearchNamespaces: gitlab.Bool(true), } projects, response, err := context.client.Projects.ListProjects(&options) if response.StatusCode == 404 { printErrorAndExit(2, "Project not found %s", err.Error()) } if err != nil { printErrorAndExit(2, "Failed to get projects %s", err.Error()) } if response.StatusCode != 200 { printErrorAndExit(2, "Failed to get projects %s", err.Error()) } if len(projects) == 0 { printErrorAndExit(2, "Project not found") } context.project = projects[0] } func enrichIssuesWithGitlab(pageMap map[string]*requirement) error { context := getGitlabContext() searchProject(context) for _, requirement := range pageMap { for k, issue := range requirement.Issues { if issue.GitlabIntern == nil { continue } if issue.GitlabIntern.ID == 0 { continue } i, err := loadIssuesFromGitlab(context, issue.GitlabIntern.ID) if err != nil { return err } requirement.Issues[k].GitlabRemote = i } } return nil } func syncFilesWithGitlab() error { err, pageData := collectStructureFromFiles(config.Path) if err != nil { return err } issuesList := []Issue{} for _, pageData := range pageData { for _, info := range pageData.Issues { issuesList = append(issuesList, info) } } context := getGitlabContext() searchProject(context) syncIssuesWithGitlab(pageData) return rewriteFiles(pageData) } func nodesEqual(l, r *yaml.Node) bool { if l.Kind == yaml.ScalarNode && r.Kind == yaml.ScalarNode { return l.Value == r.Value } panic("equals on non-scalars not implemented!") } func recursiveRemove(nodes *yaml.Node) error { if nodes.Kind == yaml.DocumentNode { recursiveRemove(nodes.Content[0]) } if nodes.Kind != yaml.MappingNode { return nil } var found int for i := 0; i < len(nodes.Content); i += 2 { if nodes.Content[i].Value == "Issues" { found = i break } if err := recursiveRemove(nodes.Content[i+1]); err != nil { return errors.New("at key " + nodes.Content[i].Value + ": " + err.Error()) } } if found > 0 { copy(nodes.Content[found:], nodes.Content[found+2:]) // Shift a[i+1:] left one index. nodes.Content[len(nodes.Content)-1] = nil // Erase last element (write zero value). nodes.Content[len(nodes.Content)-2] = nil // Erase last element (write zero value). nodes.Content = nodes.Content[:len(nodes.Content)-2] } return nil } func recursiveMerge(from, into *yaml.Node) error { if from.Kind != into.Kind { return errors.New("cannot merge nodes of different kinds") } switch from.Kind { case yaml.MappingNode: for i := 0; i < len(from.Content); i += 2 { found := false for j := 0; j < len(into.Content); j += 2 { if nodesEqual(from.Content[i], into.Content[j]) { found = true if err := recursiveMerge(from.Content[i+1], into.Content[j+1]); err != nil { return errors.New("at key " + from.Content[i].Value + ": " + err.Error()) } break } } if !found { into.Content = append(into.Content, from.Content[i:i+2]...) } } case yaml.SequenceNode: into.Content = append(into.Content, from.Content...) case yaml.DocumentNode: recursiveMerge(from.Content[0], into.Content[0]) default: return errors.New("can only merge mapping and sequence nodes") } return nil } func syncIssuesWithGitlab(pageData map[string]*requirement) { for _, pageData := range pageData { for k, info := range pageData.Issues { if info.GitlabRemote != nil { continue } if info.GitlabIntern == nil { continue } if info.GitlabIntern.ID == 0 { issue, err := createIssue(info) if err != nil { printErrorAndExit(2, "Failed to create issue %s", err.Error()) } pageData.Issues[k].GitlabRemote = issue pageData.Issues[k].GitlabIntern = new(GitlabInternalIssueStruct) pageData.Issues[k].GitlabIntern.ID = issue.IID var change yaml.Node bytes, err := yaml.Marshal(&pageData) if err != nil { fmt.Println(err) } yaml.Unmarshal(bytes, &change) recursiveRemove(pageData.OriginNode) recursiveMerge(&change, pageData.OriginNode) } } } } func createIssue(issue Issue) (*gitlab.Issue, error) { context := getGitlabContext() searchProject(context) //title := *string(issue.GitlabIntern.Title) labels := gitlab.Labels{} for _, label := range issue.GitlabIntern.Labels { labels = append(labels, label) } createionOptions := gitlab.CreateIssueOptions{ Title: gitlab.String(issue.GitlabIntern.Title), Description: gitlab.String(issue.GitlabIntern.Description), Labels: &labels, } ptr := &createionOptions gitlabIssue, response, err := context.client.Issues.CreateIssue(context.project.ID, ptr) if response.StatusCode == 401 { return gitlabIssue, err } if response.StatusCode > 299 { return gitlabIssue, err } return gitlabIssue, nil } // // context := getGitlabContext(config) // searchProject(context) // // options := &gitlab.CreateIssueOptions{ // Title: gitlab.String(issue.Title), // Status: gitlab.String(issue.Status), // Labels: gitlab.StringSlice(issue.Labels), // } // // i, _, err := context.client.Issues.CreateIssue(context.project.ID, options) // if err != nil { // return nil, err // } // // return i, nil // //} // // //func getProject(config *Configuration) *gitlab.Project { // // client := getGitlabClient(config) // // pattern := "test" // // options := gitlab.ListProjectsOptions{ // Search: &pattern, // } // // projects, _, err := client.Projects.ListProjects(&options) // if err != nil { // printErrorAndExit(2, "Failed to get projects", err.Error()) // } // // //project, _, err := client.Projects.GetProject(config.GitlabProjectID, nil) // //if err != nil { // // printErrorAndExit(2, "Failed to get project", err.Error()) // //} // // return projects[0] //}