package main /* * This file is part of Autogits. * * Copyright © 2024 SUSE LLC * * Autogits is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 2 of the License, or (at your option) any later * version. * * Autogits is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * Foobar. If not, see . */ import ( "bytes" "errors" "flag" "fmt" "io/fs" "log" "net/url" "os" "os/exec" "path" "path/filepath" "slices" "strings" transport "github.com/go-openapi/runtime/client" "src.opensuse.org/autogits/common" apiclient "src.opensuse.org/autogits/common/gitea-generated/client" "src.opensuse.org/autogits/common/gitea-generated/client/organization" "src.opensuse.org/autogits/common/gitea-generated/client/repository" "src.opensuse.org/autogits/common/gitea-generated/models" ) const commandLineHelp = ` SYNTAX devel-importer ` func printHelp(flags string) { os.Stdout.WriteString(commandLineHelp) os.Stdout.WriteString(flags) } func outputList(str string) []string { out := []string{} for _, l := range strings.Split(strings.TrimSpace(str), "\n") { l = strings.TrimSpace(l) if len(l) > 0 { out = append(out, l) } } return out } func runObsCommand(args ...string) ([]string, error) { cmd := exec.Command("osc", args...) out, err := cmd.Output() return outputList(string(out)), err } var DebugMode bool func projectMaintainer(obs *common.ObsClient, prj string) ([]string, []string) { // users, groups meta, err := obs.GetProjectMeta(prj) if err != nil { log.Panicln(err) } uids := []string{} gids := []string{} for _, p := range meta.Persons { if !slices.Contains(uids, p.UserID) { uids = append(uids, p.UserID) } } for _, g := range meta.Groups { if !slices.Contains(gids, g.GroupID) { gids = append(gids, g.GroupID) } } return uids, gids } func packageMaintainers(obs *common.ObsClient, prj, pkg string) ([]string, []string) { // users, groups meta, err := obs.GetPackageMeta(prj, pkg) if err != nil { log.Panicln(err, "FOR:", prj, "/", pkg) } uids := []string{} gids := []string{} for _, p := range meta.Persons { if !slices.Contains(uids, p.UserID) { uids = append(uids, p.UserID) } } for _, g := range meta.Groups { if !slices.Contains(gids, g.GroupID) { gids = append(gids, g.GroupID) } } return uids, gids } func listMaintainers(obs *common.ObsClient, prj string, pkgs []string) { users, groups := projectMaintainer(obs, prj) log.Println("Fetching maintainers for prj:", prj, users) for _, pkg := range pkgs { u, g := packageMaintainers(obs, prj, pkg) log.Println("maintainers for pkg:", pkg, u) users = append(users, u...) groups = append(groups, g...) } slices.Sort(users) slices.Sort(groups) users = slices.Compact(users) groups = slices.Compact(groups) log.Println("need to contact following:", strings.Join(users, ", ")) contact_email := []string{} for _, uid := range users { user, err := obs.GetUserMeta(uid) if err != nil { log.Panicln(err) } contact_email = append(contact_email, fmt.Sprintf("%s <%s>", user.Name, user.Email)) } log.Println(strings.Join(contact_email, ", ")) } func gitImporter(prj, pkg string) error { params := []string{"-p", prj, "-r", git.GetPath()} if DebugMode { params = append(params, "-l", "debug") } params = append(params, pkg) cmd := exec.Command("./git-importer", params...) if idx := slices.IndexFunc(cmd.Env, func(val string) bool { return val[0:12] == "GITEA_TOKEN=" }); idx != -1 { cmd.Env = slices.Delete(cmd.Env, idx, idx+1) } out, err := cmd.CombinedOutput() log.Println(string(out)) if code := cmd.ProcessState.ExitCode(); code != 0 { return fmt.Errorf("Non-zero exit code from git-importer: %d %w", code, err) } return nil } func cloneDevel(git common.Git, gitDir, outName, urlString string) error { url, err := url.Parse(urlString) // branch := url.Fragment url.Fragment = "" params := []string{"clone"} /* if len(branch) > 0 { params = append(params, "-b", branch) } */ params = append(params, url.String(), outName) if err != nil { return fmt.Errorf("error parsing SSH URL. %w", err) } git.GitExecOrPanic(gitDir, params...) return nil } func importRepos(packages []string) { factoryRepos := make([]*models.Repository, 0, len(packages)*2) develProjectPackages := make([]string, 0, len(packages)) for _, pkg := range packages { src_pkg_name := strings.Split(pkg, ":") repo, err := client.Repository.RepoGet( repository.NewRepoGetParams(). WithDefaults().WithOwner("pool").WithRepo(src_pkg_name[0]), r.DefaultAuthentication) if err != nil { if !errors.Is(err, &repository.RepoGetNotFound{}) { log.Panicln(err) } log.Println("Cannot find src package:", src_pkg_name) develProjectPackages = append(develProjectPackages, src_pkg_name[0]) } else { factoryRepos = append(factoryRepos, repo.Payload) } } log.Println("Num repos found:", len(factoryRepos)) if len(develProjectPackages) > 0 { log.Println("Num of repos that need to create:", len(develProjectPackages)) log.Println("Create the following packages in pool to continue:", strings.Join(develProjectPackages, " ")) if forceNonPoolPackages { log.Println(" IGNORING and will create these as non-pool packages!") } else { os.Exit(1) } } oldPackageNames := make([]string, 0, len(factoryRepos)) for _, repo := range factoryRepos { oldPackageNames = append(oldPackageNames, repo.Name) } // fork packags from pool for i := 0; i < len(oldPackageNames); { pkg := oldPackageNames[i] log.Println(" + package:", pkg) if err := gitImporter("openSUSE:Factory", pkg); err != nil { log.Println(" ** failed to import openSUSE:Factory", pkg) log.Println(" ** falling back to devel project only") develProjectPackages = append(develProjectPackages, pkg) oldPackageNames = slices.Delete(oldPackageNames, i, i+1) } else { i++ } } log.Println("adding remotes...") for i := 0; i < len(factoryRepos); i++ { pkg := factoryRepos[i] // verify that package was created by `git-importer`, or it's scmsync package and clone it fi, err := os.Stat(filepath.Join(git.GetPath(), pkg.Name)) if os.IsNotExist(err) { if slices.Contains(develProjectPackages, pkg.Name) { // failed import of former factory package continue } // scmsync? devel_project, err := devel_projects.GetDevelProject(pkg.Name) if err != nil { log.Panicln("devel project not found for", pkg.Name, "err:", err) } meta, _ := obs.GetPackageMeta(devel_project, pkg.Name) if len(meta.ScmSync) > 0 { if err2 := cloneDevel(git, "", pkg.Name, meta.ScmSync); err != nil { log.Panicln(err2) } git.GitExecOrPanic(pkg.Name, "checkout", "-B", "main") continue } // try again, should now exist if fi, err = os.Stat(filepath.Join(git.GetPath(), pkg.Name)); err != nil { log.Panicln(err) } } else if err != nil { log.Panicln(err) } else { // verify that we do not have scmsync for imported packages meta, err := obs.GetPackageMeta(prj, pkg.Name) if err != nil { log.Panicln(err) } if len(meta.ScmSync) > 0 { log.Panicln("importing an scmsync package??:", prj, pkg.Name) } } if !fi.IsDir() { log.Panicln("Expected package file should be a directory. It's not.", fi) } // add remote repos out := git.GitExecWithOutputOrPanic(pkg.Name, "remote", "show", "-n") switch pkg.Owner.UserName { case "pool": if !slices.Contains(strings.Split(out, "\n"), "pool") { out := git.GitExecWithOutputOrPanic(pkg.Name, "remote", "add", "pool", pkg.CloneURL) if len(strings.TrimSpace(out)) > 1 { log.Println(out) } } default: log.Panicln(pkg.Owner.UserName) } } for _, pkgName := range oldPackageNames { log.Println("fetching git:", pkgName) remotes := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgName, "remote", "show", "-n"), "\n") params := []string{"fetch", "--multiple"} params = append(params, remotes...) out, _ := git.GitExecWithOutput(pkgName, params...) if len(strings.TrimSpace(out)) > 1 { log.Println(out) } if slices.Contains(remotes, "origin") { log.Println(" --- scmsync already, so we are done") continue } // check if devel is ahead or behind factory and use that as reference import_branch := "factory" if len(common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgName, "rev-list", "^factory", "devel"), "\n")) > 0 { log.Println(" *** devel ahead. Swtiching branches.") import_branch = "devel" } // check that nothing is broken with the update if slices.Contains(remotes, "pool") { // check which branch is ahead branches, err := fs.ReadDir(os.DirFS(path.Join(git.GetPath(), pkgName, ".git/refs/remotes")), "pool") if err != nil { if forceBadPool { log.Println(" *** factory has no branches!!! Treating as a devel package.") develProjectPackages = append(develProjectPackages, pkgName) break } else { log.Panicln(" *** factory has no branches", branches) } } pool_branch := "factory" has_factory_devel := false has_factory_factory := false for _, branch := range branches { if branch.Name() == "factory" { has_factory_factory = true } else if branch.Name() == "devel" { has_factory_devel = true } } log.Println(branches) if has_factory_devel && has_factory_factory { if len(common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgName, "rev-list", "^pool/factory", "pool/devel"), "\n")) > 0 { log.Println(" *** pool branch devel ahead. Switching branches.") pool_branch = "devel" } } else if has_factory_devel && !has_factory_factory { pool_branch = "devel" } else if !has_factory_devel && has_factory_factory { } else { log.Panicln("branches screwed up for pkg", pkgName, branches) } // find tree object in factory branch tree := strings.TrimSpace(git.GitExecWithOutputOrPanic(pkgName, "rev-list", "-1", "--format=%T", "--no-commit-header", "pool/"+pool_branch)) log.Println("tree", tree) import_tree_commits := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgName, "rev-list", "--format=%H %T", "--no-commit-header", import_branch), "\n") found := false for i := range import_tree_commits { commit_tree := strings.Split(import_tree_commits[i], " ") if len(commit_tree) != 2 { log.Panicln("wrong format?", commit_tree) } if commit_tree[1] == tree { found = true cherry_picks := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgName, "rev-list", "--no-merges", "--reverse", "--ancestry-path", commit_tree[0]+".."+import_branch), "\n") log.Println("cherry picks", cherry_picks) git.GitExecOrPanic(pkgName, "checkout", "-B", "main", "pool/"+pool_branch) for _, pick := range cherry_picks { git.GitExecOrPanic(pkgName, "cherry-pick", pick) } break } } if !found { log.Println("*** WARNING: Cannot find same tree for pkg", pkgName, "Will use current import instead") git.GitExecOrPanic(pkgName, "checkout", "-B", "main", "heads/"+import_branch) } } else { git.GitExecOrPanic(pkgName, "checkout", "-B", "main", "heads/"+import_branch) } } for i := 0; i < len(develProjectPackages); i++ { pkg := develProjectPackages[i] meta, _ := obs.GetPackageMeta(prj, pkg) if len(meta.ScmSync) > 0 { if err2 := cloneDevel(git, "", pkg, meta.ScmSync); err2 != nil { log.Panicln(err2) } git.GitExecOrPanic(pkg, "checkout", "-B", "main") continue } else { common.PanicOnError(gitImporter(prj, pkg)) if out, err := git.GitExecWithOutput(pkg, "show-ref", "--branches"); err != nil || len(common.SplitStringNoEmpty(out, "\n")) == 0 { log.Println(" *** no branches in package. removing") develProjectPackages = slices.Delete(develProjectPackages, i, i+1) i-- continue } } // mark newer branch as main branch := "factory" if len(common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkg, "rev-list", "^factory", "devel"), "\n")) > 0 { log.Println(" *** pool branch 'devel' ahead. Switching branches.") branch = "devel" } git.GitExecOrPanic(pkg, "checkout", "-B", "main", branch) } slices.SortFunc(factoryRepos, func(a, b *models.Repository) int { if a.Name == b.Name { orgOrderNo := func(org string) int { switch org { case "pool": return 1 } return 0 // current devel to clone } return orgOrderNo(a.Owner.UserName) - orgOrderNo(b.Owner.UserName) } return strings.Compare(a.Name, b.Name) }) factoryRepos = slices.CompactFunc(factoryRepos, func(a, b *models.Repository) bool { return a.Name == b.Name }) for _, pkg := range factoryRepos { var repo *models.Repository if repoData, err := client.Repository.RepoGet(repository.NewRepoGetParams().WithOwner(org).WithRepo(pkg.Name), r.DefaultAuthentication); err != nil { // update package fork, err := client.Repository.CreateFork(repository.NewCreateForkParams(). WithOwner(pkg.Owner.UserName). WithRepo(pkg.Name). WithBody(&models.CreateForkOption{ Organization: org, }), r.DefaultAuthentication) if err != nil { log.Panicln("Error while trying to create fork from", pkg.Owner.UserName, pkg.Name, "to", org, ":", err) } repo = fork.Payload } else { repo = repoData.Payload } // branchName := repo.DefaultBranch remotes := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkg.Name, "remote", "show"), "\n") if !slices.Contains(remotes, "develorigin") { git.GitExecOrPanic(pkg.Name, "remote", "add", "develorigin", repo.SSHURL) // git.GitExecOrPanic(pkg.Name, "fetch", "devel") } if slices.Contains(remotes, "origin") { git.GitExecOrPanic(pkg.Name, "lfs", "fetch", "--all") git.GitExecOrPanic(pkg.Name, "lfs", "push", "develorigin", "--all") } git.GitExecOrPanic(pkg.Name, "push", "develorigin", "main", "-f") git.GitExec(pkg.Name, "push", "develorigin", "--delete", "factory", "devel") // git.GitExecOrPanic(pkg.Name, "checkout", "-B", "main", "devel/main") _, err := client.Repository.RepoEdit(repository.NewRepoEditParams().WithOwner(org).WithRepo(repo.Name).WithBody(&models.EditRepoOption{ DefaultBranch: "main", DefaultMergeStyle: "fast-forward-only", HasPullRequests: true, HasPackages: false, HasReleases: false, HasActions: false, AllowMerge: true, AllowRebaseMerge: false, AllowSquash: false, AllowFastForwardOnly: true, AllowRebaseUpdate: false, AllowManualMerge: true, AutodetectManualMerge: true, AllowRebase: false, DefaultAllowMaintainerEdit: true, }), r.DefaultAuthentication) if err != nil { log.Panicln("Failed to set default branch for package fork:", repo.Owner.UserName, "/", repo.Name, err) } } for _, pkg := range develProjectPackages { var repo *models.Repository if repoData, err := client.Repository.RepoGet(repository.NewRepoGetParams().WithOwner(org).WithRepo(pkg), r.DefaultAuthentication); err != nil { _, err := client.Organization.CreateOrgRepo(organization.NewCreateOrgRepoParams().WithOrg(org).WithBody( &models.CreateRepoOption{ ObjectFormatName: "sha256", AutoInit: false, Name: &pkg, DefaultBranch: "main", }), r.DefaultAuthentication, ) if err != nil { log.Panicln("Error creating new package repository:", pkg, err) } ret, err := client.Repository.RepoEdit(repository.NewRepoEditParams().WithOwner(org).WithRepo(pkg).WithBody( &models.EditRepoOption{ HasPullRequests: true, HasPackages: false, HasReleases: false, HasActions: false, AllowMerge: true, AllowRebaseMerge: false, AllowSquash: false, AllowFastForwardOnly: true, AllowRebaseUpdate: false, AllowManualMerge: true, AutodetectManualMerge: true, DefaultMergeStyle: "fast-forward-only", AllowRebase: false, DefaultAllowMaintainerEdit: true, }), r.DefaultAuthentication, ) if err != nil { log.Panicln("Failed to adjust repository:", pkg, err) } repo = ret.Payload } else { repo = repoData.Payload } remotes := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkg, "remote", "show"), "\n") if !slices.Contains(remotes, "develorigin") { git.GitExecOrPanic(pkg, "remote", "add", "develorigin", repo.SSHURL) } if slices.Contains(remotes, "origin") { git.GitExecOrPanic(pkg, "lfs", "fetch", "--all") git.GitExecOrPanic(pkg, "lfs", "push", "develorigin", "--all") } git.GitExecOrPanic(pkg, "push", "develorigin", "main", "-f") git.GitExec(pkg, "push", "develorigin", "--delete", "factory", "devel") _, err := client.Repository.RepoEdit(repository.NewRepoEditParams().WithOwner(org).WithRepo(pkg).WithBody(&models.EditRepoOption{ DefaultBranch: "main", DefaultMergeStyle: "fast-forward-only", }), r.DefaultAuthentication) if err != nil { log.Panicln("Failed to set default branch for package fork:", repo.Owner.UserName, "/", repo.Name, err) } } } func syncOrgTeams(groupName string, origTeam []common.PersonRepoMeta) []string { teamMembers := make([]common.PersonRepoMeta, len(origTeam)) copy(teamMembers, origTeam) missing := []string{} if DebugMode { log.Println("syncOrgTeams", groupName, " -> ", teamMembers) } teamsRes, err := client.Organization.OrgListTeams(organization.NewOrgListTeamsParams().WithOrg(org), r.DefaultAuthentication) if err != nil { log.Panicln("failed to list org teams", org, err) } teams := teamsRes.Payload found := false teamID := int64(0) for _, team := range teams { if team.Name == groupName { teamID = team.ID membersRes, err := client.Organization.OrgListTeamMembers(organization.NewOrgListTeamMembersParams().WithID(team.ID), r.DefaultAuthentication) if err != nil { log.Panicln("failed get team members", team.Name, err) } for _, m := range membersRes.Payload { for i := 0; i < len(teamMembers); { if teamMembers[i].UserID == m.UserName || (teamMembers[i].Role != "maintainer" && teamMembers[i].Role != "") { teamMembers = slices.Delete(teamMembers, i, i+1) } else { i++ } } } found = true break } } if !found { team, err := client.Organization.OrgCreateTeam(organization.NewOrgCreateTeamParams().WithOrg(org).WithBody(&models.CreateTeamOption{ CanCreateOrgRepo: false, IncludesAllRepositories: true, Permission: "write", Name: &groupName, Units: []string{"repo.code"}, UnitsMap: map[string]string{"repo.code": "write"}, }), r.DefaultAuthentication) if err != nil { log.Panicln("can't create orgteam", org, err) } teamID = team.Payload.ID } if DebugMode && len(teamMembers) > 0 { log.Println("missing team members:", teamMembers) } for _, user := range teamMembers { _, err := client.Organization.OrgAddTeamMember(organization.NewOrgAddTeamMemberParams().WithID(teamID).WithUsername(user.UserID), r.DefaultAuthentication) if err != nil { if _, notFound := err.(*organization.OrgAddTeamMemberNotFound); !notFound { log.Panicln(err) } else { if DebugMode { log.Println("can't add user to group:", groupName, user.UserID) } missing = append(missing, user.UserID) } } } if DebugMode { log.Println("org team synced", groupName) } return missing } func syncOrgOwners(uids []common.PersonRepoMeta) []string { return syncOrgTeams("Owners", append(uids, common.PersonRepoMeta{UserID: "autogits-devel"})) } func syncPackageCollaborators(pkg string, orig_uids []common.PersonRepoMeta) []string { missing := []string{} uids := make([]common.PersonRepoMeta, len(orig_uids)) copy(uids, orig_uids) collab, err := client.Repository.RepoListCollaborators(repository.NewRepoListCollaboratorsParams().WithOwner(org).WithRepo(pkg), r.DefaultAuthentication) if err != nil { if errors.Is(err, &repository.RepoListCollaboratorsNotFound{}) { return missing } log.Panicln(err) } for _, u := range collab.Payload { for i := range uids { if uids[i].UserID == u.UserName { uids = slices.Delete(uids, i, i+1) break } } } if DebugMode && len(uids) > 0 { log.Println("missing collabs for", pkg, ":", uids) } for _, u := range uids { _, err := client.Repository.RepoAddCollaborator(repository.NewRepoAddCollaboratorParams().WithOwner(org).WithRepo(pkg).WithBody(&models.AddCollaboratorOption{ Permission: "write", }).WithCollaborator(u.UserID), r.DefaultAuthentication) if err != nil { if _, notFound := err.(*repository.RepoAddCollaboratorUnprocessableEntity); notFound { if DebugMode { log.Println("can't add user to collaborators:", u.UserID) } missing = append(missing, u.UserID) } else { log.Println(err) } } } return missing } func syncMaintainersToGitea(pkgs []string) { maintainers := common.MaintainershipMap{ Data: map[string][]string{}, } prjMeta, err := obs.GetProjectMeta(prj) if err != nil { log.Panicln("failed to get project meta", prj, err) } missingDevs := []string{} devs := []string{} for _, group := range prjMeta.Groups { if group.GroupID == "factory-maintainers" { log.Println("Ignoring factory-maintainers") continue } teamMembers, err := obs.GetGroupMeta(group.GroupID) if err != nil { log.Panicln("failed to get group", err) } if DebugMode { log.Println("syncing", group.GroupID, teamMembers.Persons) } missingDevs = append(missingDevs, syncOrgTeams(group.GroupID, teamMembers.Persons.Persons)...) for _, m := range teamMembers.Persons.Persons { devs = append(devs, m.UserID) } } for _, p := range prjMeta.Persons { devs = append(devs, p.UserID) } missingDevs = append(missingDevs, syncOrgOwners(prjMeta.Persons)...) maintainers.Data[""] = devs for _, pkg := range pkgs { pkgMeta, err := obs.GetPackageMeta(prj, pkg) if err != nil { log.Panicln("failed to get package meta", prj, pkg, err) } missingDevs = append(missingDevs, syncPackageCollaborators(pkg, pkgMeta.Persons)...) devs := []string{} for _, m := range pkgMeta.Persons { devs = append(devs, m.UserID) } maintainers.Data[pkg] = devs } createPrjGit() file, err := os.Create(path.Join(git.GetPath(), common.DefaultGitPrj, common.MaintainershipFile)) if err != nil { log.Println(" *** Cannot create maintainership file:", err) } else { maintainers.WriteMaintainershipFile(file) file.Close() status, err := git.GitStatus(common.DefaultGitPrj) if err != nil { log.Panicln(err) } for _, s := range status { if s.Path == common.MaintainershipFile && s.Status == common.GitStatus_Untracked { git.GitExecOrPanic(common.DefaultGitPrj, "add", common.MaintainershipFile) git.GitExecOrPanic(common.DefaultGitPrj, "commit", "-m", "Initial sync of maintainership with OBS") git.GitExecOrPanic(common.DefaultGitPrj, "push") } } if l := len(common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(common.DefaultGitPrj, "status", "--porcelain=2"), "\n")); l > 0 { } } slices.Sort(missingDevs) log.Println("Users without Gitea accounts:", slices.Compact(missingDevs)) } func createPrjGit() { if _, err := os.Stat(path.Join(git.GetPath(), common.DefaultGitPrj)); errors.Is(err, os.ErrNotExist) { if err := git.GitExec("", "clone", "gitea@src.opensuse.org:"+org+"/_ObsPrj.git", common.DefaultGitPrj); err != nil { repoName := common.DefaultGitPrj _, err := client.Organization.CreateOrgRepo(organization.NewCreateOrgRepoParams().WithOrg(org).WithBody( &models.CreateRepoOption{ AutoInit: false, Name: &repoName, ObjectFormatName: "sha256", }), r.DefaultAuthentication) if err != nil { log.Panicln(err) } git.GitExecOrPanic("", "clone", "gitea@src.opensuse.org:"+org+"/_ObsPrj.git", common.DefaultGitPrj) config, err := obs.ProjectConfig(prj) if err != nil { log.Panicln(err) } file, err := os.Create(path.Join(git.GetPath(), common.DefaultGitPrj, "_config")) if err != nil { log.Panicln(err) } config = strings.TrimSpace(config) if len(config) > 1 { file.Write([]byte(config)) file.Close() git.GitExecOrPanic(common.DefaultGitPrj, "add", "_config") } file, err = os.Create(path.Join(git.GetPath(), common.DefaultGitPrj, "staging.config")) if err != nil { log.Panicln(err) } file.WriteString("{\n // Reference build project\n \"ObsProject\": \""+prj+"\",\n}\n") file.Close() git.GitExecOrPanic(common.DefaultGitPrj, "add", "staging.config") if file, err = os.Create(path.Join(git.GetPath(), common.DefaultGitPrj, "workflow.config")); err != nil { log.Panicln(err) } file.WriteString("{\n \"Workflows\": [\"direct\", \"pr\"],\n \"Organization\": \""+org+"\",\n}\n") file.Close() git.GitExecOrPanic(common.DefaultGitPrj, "add", "workflow.config") } } } var client *apiclient.GiteaAPI var r *transport.Runtime var git common.Git var obs *common.ObsClient var prj, org string var forceBadPool bool var forceNonPoolPackages bool var devel_projects common.DevelProjects func main() { if err := common.RequireGiteaSecretToken(); err != nil { log.Panicln("Missing GITEA_TOKEN") } if err := common.RequireObsSecretToken(); err != nil { log.Panicln("Missing OBS_PASSWORD and/or OBS_USER") } flags := flag.NewFlagSet("devel-importer", flag.ContinueOnError) helpString := new(bytes.Buffer) flags.SetOutput(helpString) //workflowConfig := flag.String("config", "", "Repository and workflow definition file") giteaHost := flags.String("gitea", "src.opensuse.org", "Gitea instance") obsUrl := flags.String("obs-url", "https://api.opensuse.org", "OBS API Url") //rabbitUrl := flag.String("url", "amqps://rabbit.opensuse.org", "URL for RabbitMQ instance") flags.BoolVar(&DebugMode, "debug", false, "Extra debugging information") // revNew := flag.Int("nrevs", 20, "Number of new revisions in factory branch. Indicator of broken history import") purgeOnly := flags.Bool("purge-only", false, "Purges package repositories on Gitea. Use with caution") debugGitPath := flags.String("git-path", "", "Path for temporary git directory. Only used if DebugMode") getMaintainers := flags.Bool("maintainers-only", false, "Get maintainers only and exit") syncMaintainers := flags.Bool("sync-maintainers-only", false, "Sync maintainers to Gitea and exit") flags.BoolVar(&forceBadPool, "bad-pool", false, "Force packages if pool has no branches due to bad import") flags.BoolVar(&forceNonPoolPackages, "non-pool", false, "Allow packages that are not in pool to be created. WARNING: Can't add to factory later!") if help := flags.Parse(os.Args[1:]); help == flag.ErrHelp || flags.NArg() != 2 { printHelp(helpString.String()) return } r = transport.New(*giteaHost, apiclient.DefaultBasePath, [](string){"https"}) r.DefaultAuthentication = transport.BearerToken(common.GetGiteaToken()) // r.SetDebug(true) client = apiclient.New(r, nil) obs, _ = common.NewObsClient(*obsUrl) var gh common.GitHandlerGenerator var err error devel_projects, err = common.FetchDevelProjects() if err != nil { log.Panic("Cannot load devel projects:", err) } log.Println("# devel projects loaded:", len(devel_projects)) if DebugMode { if len(*debugGitPath) > 0 { gh, err = common.AllocateGitWorkTree(*debugGitPath, "Autogits - Devel Importer", "not.exist") if err != nil { log.Panicln(err) } } } else { dir, _ := os.MkdirTemp(os.TempDir(), "devel-importer") gh, err = common.AllocateGitWorkTree(dir, "Autogits - Devel Importer", "not.exist") if err != nil { log.Panicln("Failed to allocate git handler", err) } } prj = flags.Arg(0) org = flags.Arg(1) packages, err := runObsCommand("ls", prj) for i := 0; i < len(packages); { if strings.Contains(packages[i], ":") { packages = slices.Delete(packages, i, i+1) } else { i++ } } git, err = gh.CreateGitHandler(org) if err != nil { log.Panicln("Cannot create git", err) } defer git.Close() log.Println(" - working directory:" + git.GetPath()) /* for _, pkg := range packages { if _, err := client.Organization.CreateOrgRepo(organization.NewCreateOrgRepoParams().WithOrg(org).WithBody( &models.CreateRepoOption{ ObjectFormatName: "sha256", AutoInit: false, Name: &pkg, DefaultBranch: "main", }), r.DefaultAuthentication, ); err != nil { log.Println(err) } } */ if *getMaintainers { listMaintainers(obs, prj, packages) return } if *syncMaintainers { syncMaintainersToGitea(packages) return } if err != nil { log.Printf("Cannot list packages for project '%s'. Err: %v\n", prj, err) return } slices.Sort(packages) for i := range packages { packages[i] = strings.Split(packages[i], ":")[0] } packages = slices.Compact(packages) log.Printf("%d packages: %s\n\n", len(packages), strings.Join(packages, " ")) if *purgeOnly { log.Println("Purging repositories...") for _, pkg := range packages { client.Repository.RepoDelete(repository.NewRepoDeleteParams().WithOwner(org).WithRepo(pkg), r.DefaultAuthentication) } os.Exit(10) } importRepos(packages) syncMaintainersToGitea(packages) }