2024-08-07 01:37:55 +02:00
|
|
|
package main
|
|
|
|
|
2024-09-10 18:24:41 +02:00
|
|
|
/*
|
|
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2024-08-07 01:37:55 +02:00
|
|
|
import (
|
2025-02-24 18:55:37 +01:00
|
|
|
"bytes"
|
2024-08-07 01:37:55 +02:00
|
|
|
"errors"
|
2024-08-09 17:53:18 +02:00
|
|
|
"flag"
|
2025-02-24 18:55:37 +01:00
|
|
|
"fmt"
|
2024-09-16 13:10:25 +02:00
|
|
|
"log"
|
2024-08-07 01:37:55 +02:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
2024-09-18 11:47:42 +02:00
|
|
|
"path/filepath"
|
2024-08-09 17:53:18 +02:00
|
|
|
"slices"
|
2024-08-07 01:37:55 +02:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
transport "github.com/go-openapi/runtime/client"
|
|
|
|
"src.opensuse.org/autogits/common"
|
|
|
|
apiclient "src.opensuse.org/autogits/common/gitea-generated/client"
|
2024-08-09 17:53:18 +02:00
|
|
|
"src.opensuse.org/autogits/common/gitea-generated/client/organization"
|
2024-08-07 01:37:55 +02:00
|
|
|
"src.opensuse.org/autogits/common/gitea-generated/client/repository"
|
|
|
|
"src.opensuse.org/autogits/common/gitea-generated/models"
|
|
|
|
)
|
|
|
|
|
|
|
|
const commandLineHelp = `
|
2025-02-24 18:55:37 +01:00
|
|
|
SYNTAX
|
|
|
|
devel-importer <obs-project> <gitea-org>
|
2024-08-07 01:37:55 +02:00
|
|
|
|
2025-02-24 18:55:37 +01:00
|
|
|
`
|
|
|
|
|
|
|
|
func printHelp(flags string) {
|
|
|
|
os.Stdout.WriteString(commandLineHelp)
|
|
|
|
os.Stdout.WriteString(flags)
|
2024-08-07 01:37:55 +02:00
|
|
|
}
|
|
|
|
|
2025-02-28 17:36:35 +01:00
|
|
|
func outputList(str string) []string {
|
|
|
|
out := []string{}
|
2025-02-24 18:55:37 +01:00
|
|
|
|
2025-02-28 17:36:35 +01:00
|
|
|
for _, l := range strings.Split(strings.TrimSpace(str), "\n") {
|
|
|
|
l = strings.TrimSpace(l)
|
|
|
|
if len(l) > 0 {
|
|
|
|
out = append(out, l)
|
2025-02-24 18:55:37 +01:00
|
|
|
}
|
|
|
|
}
|
2025-02-28 17:36:35 +01:00
|
|
|
return out
|
|
|
|
}
|
2025-02-24 18:55:37 +01:00
|
|
|
|
2025-02-28 17:36:35 +01:00
|
|
|
func runObsCommand(args ...string) ([]string, error) {
|
|
|
|
cmd := exec.Command("osc", args...)
|
|
|
|
out, err := cmd.Output()
|
|
|
|
|
|
|
|
return outputList(string(out)), err
|
2024-08-07 01:37:55 +02:00
|
|
|
}
|
|
|
|
|
2024-09-17 10:56:31 +02:00
|
|
|
var DebugMode bool
|
|
|
|
|
2025-02-26 18:10:42 +01:00
|
|
|
func projectMaintainer(obs *common.ObsClient, prj string) ([]string, []string) { // users, groups
|
2025-02-23 10:36:29 +01:00
|
|
|
meta, err := obs.GetProjectMeta(prj)
|
|
|
|
if err != nil {
|
|
|
|
log.Panicln(err)
|
|
|
|
}
|
2025-02-24 18:55:37 +01:00
|
|
|
|
|
|
|
uids := []string{}
|
2025-02-26 18:10:42 +01:00
|
|
|
gids := []string{}
|
2025-02-24 18:55:37 +01:00
|
|
|
|
2025-02-23 10:36:29 +01:00
|
|
|
for _, p := range meta.Persons {
|
2025-02-24 18:55:37 +01:00
|
|
|
if !slices.Contains(uids, p.UserID) {
|
|
|
|
uids = append(uids, p.UserID)
|
|
|
|
}
|
2025-02-23 10:36:29 +01:00
|
|
|
}
|
|
|
|
for _, g := range meta.Groups {
|
2025-02-26 18:10:42 +01:00
|
|
|
if !slices.Contains(gids, g.GroupID) {
|
|
|
|
gids = append(gids, g.GroupID)
|
|
|
|
}
|
2025-02-23 10:36:29 +01:00
|
|
|
}
|
2025-02-24 18:55:37 +01:00
|
|
|
|
2025-02-26 18:10:42 +01:00
|
|
|
return uids, gids
|
|
|
|
}
|
2025-02-24 18:55:37 +01:00
|
|
|
|
2025-02-26 18:10:42 +01:00
|
|
|
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)
|
2025-02-24 18:55:37 +01:00
|
|
|
}
|
|
|
|
}
|
2025-02-26 18:10:42 +01:00
|
|
|
for _, g := range meta.Groups {
|
|
|
|
if !slices.Contains(gids, g.GroupID) {
|
|
|
|
gids = append(gids, g.GroupID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return uids, gids
|
|
|
|
}
|
2025-02-24 18:55:37 +01:00
|
|
|
|
2025-02-26 18:10:42 +01:00
|
|
|
func listMaintainers(obs *common.ObsClient, prj string, pkgs []string) {
|
|
|
|
users, groups := projectMaintainer(obs, prj)
|
2025-02-28 17:36:35 +01:00
|
|
|
log.Println("Fetching maintainers for prj:", prj, users)
|
2025-02-26 18:10:42 +01:00
|
|
|
|
|
|
|
for _, pkg := range pkgs {
|
|
|
|
u, g := packageMaintainers(obs, prj, pkg)
|
2025-02-28 17:36:35 +01:00
|
|
|
log.Println("maintainers for pkg:", pkg, u)
|
2025-02-26 18:10:42 +01:00
|
|
|
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, ", "))
|
2025-02-24 18:55:37 +01:00
|
|
|
|
|
|
|
contact_email := []string{}
|
2025-02-26 18:10:42 +01:00
|
|
|
for _, uid := range users {
|
2025-02-24 18:55:37 +01:00
|
|
|
user, err := obs.GetUserMeta(uid)
|
|
|
|
if err != nil {
|
2025-02-26 18:10:42 +01:00
|
|
|
log.Panicln(err)
|
2025-02-24 18:55:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
contact_email = append(contact_email, fmt.Sprintf("%s <%s>", user.Name, user.Email))
|
|
|
|
}
|
|
|
|
log.Println(strings.Join(contact_email, ", "))
|
2025-02-23 10:36:29 +01:00
|
|
|
}
|
|
|
|
|
2025-03-01 00:26:08 +01:00
|
|
|
func importRepos(packages []string) {
|
2024-09-17 15:06:29 +02:00
|
|
|
factoryRepos := make([]*models.Repository, 0, len(packages)*2)
|
|
|
|
develProjectPackages := make([]string, 0, len(packages))
|
2024-08-07 01:37:55 +02:00
|
|
|
for _, pkg := range packages {
|
2024-09-16 16:05:46 +02:00
|
|
|
src_pkg_name := strings.Split(pkg, ":")
|
2024-08-07 01:37:55 +02:00
|
|
|
repo, err := client.Repository.RepoGet(
|
|
|
|
repository.NewRepoGetParams().
|
2024-09-16 16:05:46 +02:00
|
|
|
WithDefaults().WithOwner("pool").WithRepo(src_pkg_name[0]),
|
2024-08-07 01:37:55 +02:00
|
|
|
r.DefaultAuthentication)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if !errors.Is(err, &repository.RepoGetNotFound{}) {
|
2024-09-16 16:05:46 +02:00
|
|
|
log.Panicln(err)
|
2024-08-07 01:37:55 +02:00
|
|
|
}
|
|
|
|
|
2024-09-16 16:05:46 +02:00
|
|
|
log.Println("Cannot find src package:", src_pkg_name)
|
2024-09-17 15:06:29 +02:00
|
|
|
develProjectPackages = append(develProjectPackages, src_pkg_name[0])
|
2024-08-07 01:37:55 +02:00
|
|
|
} else {
|
2024-09-17 15:06:29 +02:00
|
|
|
factoryRepos = append(factoryRepos, repo.Payload)
|
2024-08-09 17:53:18 +02:00
|
|
|
}
|
2024-09-17 15:06:29 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
log.Println("Num repos found:", len(factoryRepos))
|
|
|
|
|
|
|
|
oldPackageNames := make([]string, 0, len(factoryRepos))
|
|
|
|
for _, repo := range factoryRepos {
|
|
|
|
oldPackageNames = append(oldPackageNames, repo.Name)
|
2024-08-09 17:53:18 +02:00
|
|
|
}
|
2025-03-03 15:03:43 +01:00
|
|
|
|
|
|
|
// fork packags from pool
|
|
|
|
for _, pkg := range oldPackageNames {
|
|
|
|
log.Println(" + package:", pkg)
|
|
|
|
cmd := exec.Command("./git-importer", "-r", git.GetPath(), pkg)
|
|
|
|
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 err != nil {
|
|
|
|
log.Println("Error returned by importer.", err)
|
2025-02-26 18:10:42 +01:00
|
|
|
}
|
2025-03-03 15:03:43 +01:00
|
|
|
}
|
|
|
|
|
2024-09-17 15:06:29 +02:00
|
|
|
log.Println("adding remotes...")
|
|
|
|
for i := range factoryRepos {
|
|
|
|
pkg := factoryRepos[i]
|
2024-08-09 17:53:18 +02:00
|
|
|
|
2024-09-18 11:47:42 +02:00
|
|
|
// verify that package was created by `git-importer`, or it's scmsync package and clone it
|
2025-02-21 17:18:37 +01:00
|
|
|
fi, err := os.Stat(filepath.Join(git.GetPath(), pkg.Name))
|
2024-09-18 11:47:42 +02:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
// scmsync?
|
|
|
|
devel_project, err := runObsCommand("develproject", "openSUSE:Factory", pkg.Name)
|
2025-02-24 18:55:37 +01:00
|
|
|
if err != nil || len(devel_project) != 1 {
|
|
|
|
log.Panicln("devel project len:", len(devel_project), "err:", err)
|
2024-09-18 11:47:42 +02:00
|
|
|
}
|
2025-02-24 18:55:37 +01:00
|
|
|
d := strings.Split(devel_project[0], "/")
|
2024-09-18 11:47:42 +02:00
|
|
|
if len(d) != 2 {
|
|
|
|
log.Panicln("expected devel project/package. got:", d)
|
|
|
|
}
|
|
|
|
meta, _ := obs.GetPackageMeta(d[0], d[1])
|
|
|
|
if len(meta.ScmSync) > 0 {
|
|
|
|
if err2 := git.CloneDevel("", pkg.Name, meta.ScmSync); err != nil {
|
|
|
|
log.Panicln(err2)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// try again, should now exist
|
2025-02-21 17:18:37 +01:00
|
|
|
if fi, err = os.Stat(filepath.Join(git.GetPath(), pkg.Name)); err != nil {
|
2024-09-18 11:47:42 +02:00
|
|
|
log.Panicln(err)
|
|
|
|
}
|
|
|
|
} else if err != nil {
|
|
|
|
log.Panicln(err)
|
2025-02-28 17:36:35 +01:00
|
|
|
} 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)
|
|
|
|
}
|
2024-09-18 11:47:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if !fi.IsDir() {
|
|
|
|
log.Panicln("Expected package file should be a directory. It's not.", fi)
|
|
|
|
}
|
|
|
|
|
2024-08-11 23:27:34 +02:00
|
|
|
// add remote repos
|
2024-09-17 10:56:31 +02:00
|
|
|
out := git.GitExecWithOutputOrPanic(pkg.Name, "remote", "show", "-n")
|
2024-09-17 15:06:29 +02:00
|
|
|
switch pkg.Owner.UserName {
|
|
|
|
case "pool":
|
|
|
|
if !slices.Contains(strings.Split(out, "\n"), "factory") {
|
|
|
|
out := git.GitExecWithOutputOrPanic(pkg.Name, "remote", "add", "factory", pkg.CloneURL)
|
|
|
|
if len(strings.TrimSpace(out)) > 1 {
|
|
|
|
log.Println(out)
|
|
|
|
}
|
2024-08-11 23:27:34 +02:00
|
|
|
}
|
2025-02-26 18:10:42 +01:00
|
|
|
default:
|
|
|
|
log.Panicln(pkg.Owner.UserName)
|
2024-08-09 17:53:18 +02:00
|
|
|
}
|
2024-09-17 15:06:29 +02:00
|
|
|
}
|
2024-08-09 17:53:18 +02:00
|
|
|
|
2024-09-17 15:06:29 +02:00
|
|
|
for _, pkgName := range oldPackageNames {
|
|
|
|
log.Println("fetching git:", pkgName)
|
2024-09-18 11:47:42 +02:00
|
|
|
remotes := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgName, "remote", "show", "-n"), "\n")
|
2025-02-28 17:36:35 +01:00
|
|
|
|
2025-03-03 15:03:43 +01:00
|
|
|
params := []string{"fetch", "--multiple"}
|
|
|
|
params = append(params, remotes...)
|
|
|
|
out := git.GitExecWithOutputOrPanic(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
|
|
|
|
}
|
2025-02-28 17:36:35 +01:00
|
|
|
|
|
|
|
// 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"
|
2024-08-11 23:27:34 +02:00
|
|
|
}
|
2024-08-09 17:53:18 +02:00
|
|
|
|
2024-08-11 23:27:34 +02:00
|
|
|
// check that nothing is broken with the update
|
2024-09-18 13:51:52 +02:00
|
|
|
if slices.Contains(remotes, "factory") {
|
2025-02-28 17:36:35 +01:00
|
|
|
// check which branch is ahead
|
|
|
|
branches := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgName, "branch", "-r"), "\n")
|
|
|
|
pool_branch := "factory"
|
|
|
|
|
|
|
|
if slices.Contains(branches, "factory/devel") && slices.Contains(branches, "factory/factory") {
|
|
|
|
if len(common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgName, "rev-list", "^factory/factory", "factory/devel"), "\n")) > 0 {
|
|
|
|
log.Println(" *** pool branch devel ahead. Switching branches.")
|
|
|
|
pool_branch = "devel"
|
|
|
|
}
|
|
|
|
} else if slices.Contains(branches, "factory/devel") && !slices.Contains(branches, "factory/factory") {
|
|
|
|
pool_branch = "devel"
|
|
|
|
} else if !slices.Contains(branches, "factory/devel") && slices.Contains(branches, "factory/factory") {
|
2024-09-18 17:17:24 +02:00
|
|
|
|
2025-02-28 17:36:35 +01:00
|
|
|
} else {
|
|
|
|
log.Panicln("branches screwed up for pkg", pkgName, branches)
|
|
|
|
}
|
2024-09-18 17:17:24 +02:00
|
|
|
|
2025-02-28 17:36:35 +01:00
|
|
|
// find tree object in factory branch
|
|
|
|
tree := strings.TrimSpace(git.GitExecWithOutputOrPanic(pkgName, "rev-list", "-1", "--format=%T", "--no-commit-header", "factory/"+import_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)
|
2024-09-18 17:17:24 +02:00
|
|
|
}
|
2025-02-28 17:36:35 +01:00
|
|
|
if commit_tree[1] == tree {
|
|
|
|
found = true
|
|
|
|
cherry_picks := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgName, "rev-list", "--reverse", "--ancestry-path", commit_tree[0]+".."+import_branch), "\n")
|
|
|
|
|
|
|
|
log.Println("cherry picks", cherry_picks)
|
|
|
|
git.GitExecOrPanic(pkgName, "checkout", "-B", "main", pool_branch)
|
|
|
|
for _, pick := range cherry_picks {
|
|
|
|
git.GitExecOrPanic(pkgName, "cherry-pick", pick)
|
2024-09-18 17:17:24 +02:00
|
|
|
}
|
2025-02-28 17:36:35 +01:00
|
|
|
break
|
2024-09-18 17:17:24 +02:00
|
|
|
}
|
2024-09-18 13:51:52 +02:00
|
|
|
}
|
2025-02-28 17:36:35 +01:00
|
|
|
|
|
|
|
if !found {
|
2025-03-03 15:03:43 +01:00
|
|
|
log.Println("*** WARNING: Cannot find same tree for pkg", pkgName, "Will use current import instead")
|
|
|
|
git.GitExecOrPanic(pkgName, "checkout", "-B", "main", import_branch)
|
2025-02-28 17:36:35 +01:00
|
|
|
}
|
|
|
|
} else {
|
2025-03-03 15:03:43 +01:00
|
|
|
git.GitExecOrPanic(pkgName, "checkout", "-B", "main", import_branch)
|
2024-08-11 23:27:34 +02:00
|
|
|
}
|
2024-08-13 09:28:04 +02:00
|
|
|
}
|
|
|
|
|
2025-02-26 18:10:42 +01:00
|
|
|
for _, pkg := range develProjectPackages {
|
2025-02-28 17:36:35 +01:00
|
|
|
meta, _ := obs.GetPackageMeta(prj, pkg)
|
|
|
|
if len(meta.ScmSync) > 0 {
|
|
|
|
if err2 := git.CloneDevel("", pkg, meta.ScmSync); err2 != nil {
|
|
|
|
log.Panicln(err2)
|
|
|
|
}
|
|
|
|
} else {
|
2025-03-03 15:03:43 +01:00
|
|
|
cmd := exec.Command("./git-importer", "-p", prj, "-r", git.GetPath(), pkg)
|
|
|
|
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 err != nil {
|
|
|
|
log.Panicln("Error returned by importer.", err)
|
|
|
|
}
|
2025-02-26 18:10:42 +01:00
|
|
|
}
|
2024-08-13 09:28:04 +02:00
|
|
|
|
2025-02-28 17:36:35 +01:00
|
|
|
// 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)
|
2024-08-13 09:28:04 +02:00
|
|
|
}
|
2024-08-11 23:27:34 +02:00
|
|
|
|
2024-09-19 19:00:56 +02:00
|
|
|
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
|
|
|
|
})
|
|
|
|
|
2024-09-17 15:06:29 +02:00
|
|
|
for _, pkg := range factoryRepos {
|
2024-08-11 23:27:34 +02:00
|
|
|
// update package
|
2024-08-13 09:28:04 +02:00
|
|
|
fork, err := client.Repository.CreateFork(repository.NewCreateForkParams().
|
2024-09-19 19:00:56 +02:00
|
|
|
WithOwner(pkg.Owner.UserName).
|
2024-08-13 09:28:04 +02:00
|
|
|
WithRepo(pkg.Name).
|
|
|
|
WithBody(&models.CreateForkOption{
|
|
|
|
Organization: org,
|
|
|
|
}), r.DefaultAuthentication)
|
|
|
|
if err != nil {
|
2024-09-18 17:17:24 +02:00
|
|
|
log.Panicln("Error while trying to create fork from pool", pkg.Name, err)
|
2024-08-13 09:28:04 +02:00
|
|
|
}
|
2024-08-11 23:27:34 +02:00
|
|
|
|
2024-08-13 09:28:04 +02:00
|
|
|
repo := fork.Payload
|
2025-02-28 17:36:35 +01:00
|
|
|
// branchName := repo.DefaultBranch
|
2024-08-11 23:27:34 +02:00
|
|
|
|
2024-09-17 10:56:31 +02:00
|
|
|
remotes := git.GitExecWithOutputOrPanic(pkg.Name, "remote", "show")
|
2024-08-13 09:28:04 +02:00
|
|
|
if !slices.Contains(strings.Split(remotes, "\n"), "devel") {
|
2024-09-17 10:56:31 +02:00
|
|
|
git.GitExecOrPanic(pkg.Name, "remote", "add", "devel", repo.SSHURL)
|
2025-02-28 17:36:35 +01:00
|
|
|
// git.GitExecOrPanic(pkg.Name, "fetch", "devel")
|
2024-08-13 09:28:04 +02:00
|
|
|
}
|
2025-03-03 15:03:43 +01:00
|
|
|
git.GitExecOrPanic(pkg.Name, "push", "devel", "main")
|
|
|
|
// 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: false,
|
|
|
|
AllowRebase: false,
|
|
|
|
DefaultAllowMaintainerEdit: true,
|
|
|
|
}), r.DefaultAuthentication)
|
2024-08-13 09:28:04 +02:00
|
|
|
|
2025-03-03 15:03:43 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Panicln("Failed to set default branch for package fork:", repo.Owner.UserName, "/", repo.Name, err)
|
|
|
|
}
|
2024-08-09 17:53:18 +02:00
|
|
|
}
|
|
|
|
|
2024-09-17 15:06:29 +02:00
|
|
|
for _, pkg := range develProjectPackages {
|
2025-02-26 18:10:42 +01:00
|
|
|
_, err := client.Organization.CreateOrgRepo(organization.NewCreateOrgRepoParams().WithOrg(org).WithBody(
|
2024-08-13 09:28:04 +02:00
|
|
|
&models.CreateRepoOption{
|
|
|
|
ObjectFormatName: "sha256",
|
|
|
|
AutoInit: false,
|
|
|
|
Name: &pkg,
|
|
|
|
DefaultBranch: "main",
|
|
|
|
}),
|
|
|
|
r.DefaultAuthentication,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
2024-09-16 16:05:46 +02:00
|
|
|
log.Panicln("Error creating new package repository:", pkg, err)
|
2024-08-13 09:28:04 +02:00
|
|
|
}
|
|
|
|
|
2025-02-26 18:10:42 +01:00
|
|
|
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: false,
|
|
|
|
DefaultMergeStyle: "fast-forward-only",
|
|
|
|
AllowRebase: false,
|
|
|
|
DefaultAllowMaintainerEdit: true,
|
|
|
|
}),
|
|
|
|
r.DefaultAuthentication,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Panicln("Failed to adjust repository:", pkg, err)
|
|
|
|
}
|
|
|
|
|
2024-08-13 09:28:04 +02:00
|
|
|
repo := ret.Payload
|
2024-09-17 10:56:31 +02:00
|
|
|
remotes := git.GitExecWithOutputOrPanic(pkg, "remote", "show")
|
2024-08-13 09:28:04 +02:00
|
|
|
if !slices.Contains(strings.Split(remotes, "\n"), "devel") {
|
2024-09-17 10:56:31 +02:00
|
|
|
git.GitExecOrPanic(pkg, "remote", "add", "devel", repo.SSHURL)
|
2024-08-13 09:28:04 +02:00
|
|
|
}
|
2025-03-03 15:03:43 +01:00
|
|
|
git.GitExecOrPanic(pkg, "push", "devel", "main")
|
2024-08-13 09:28:04 +02:00
|
|
|
|
|
|
|
_, err = client.Repository.RepoEdit(repository.NewRepoEditParams().WithOwner(org).WithRepo(pkg).WithBody(&models.EditRepoOption{
|
|
|
|
DefaultBranch: "main",
|
|
|
|
DefaultMergeStyle: "fast-forward-only",
|
|
|
|
}), r.DefaultAuthentication)
|
|
|
|
|
|
|
|
if err != nil {
|
2024-09-16 16:05:46 +02:00
|
|
|
log.Panicln("Failed to set default branch for package fork:", repo.Owner.UserName, "/", repo.Name, err)
|
2024-08-13 09:28:04 +02:00
|
|
|
}
|
2024-08-07 01:37:55 +02:00
|
|
|
}
|
2025-02-28 17:36:35 +01:00
|
|
|
}
|
|
|
|
|
2025-03-01 00:26:08 +01:00
|
|
|
func syncOrgTeams(groupName string, teamMembers []common.PersonRepoMeta) []string {
|
|
|
|
missing := []string{}
|
|
|
|
if DebugMode {
|
|
|
|
log.Println("syncOrgTeams", groupName, " -> ", teamMembers)
|
|
|
|
}
|
2025-02-28 17:36:35 +01:00
|
|
|
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 {
|
2025-03-01 00:26:08 +01:00
|
|
|
teamID = team.ID
|
2025-02-28 17:36:35 +01:00
|
|
|
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 {
|
2025-03-04 18:36:41 +01:00
|
|
|
for i := 0; i < len(teamMembers); {
|
|
|
|
if teamMembers[i].UserID == m.UserName || (teamMembers[i].Role != "maintainer" && teamMembers[i].Role != "") {
|
2025-02-28 17:36:35 +01:00
|
|
|
teamMembers = slices.Delete(teamMembers, i, i+1)
|
2025-03-04 18:36:41 +01:00
|
|
|
} else {
|
|
|
|
i++
|
2025-02-28 17:36:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2025-03-01 00:26:08 +01:00
|
|
|
Units: []string{"repo.code"},
|
|
|
|
UnitsMap: map[string]string{"repo.code": "write"},
|
2025-02-28 17:36:35 +01:00
|
|
|
}), r.DefaultAuthentication)
|
|
|
|
if err != nil {
|
|
|
|
log.Panicln("can't create orgteam", org, err)
|
|
|
|
}
|
|
|
|
teamID = team.Payload.ID
|
|
|
|
}
|
|
|
|
|
2025-03-04 18:36:41 +01:00
|
|
|
if DebugMode && len(teamMembers) > 0 {
|
|
|
|
log.Println("missing team members:", teamMembers)
|
|
|
|
}
|
|
|
|
|
2025-02-28 17:36:35 +01:00
|
|
|
for _, user := range teamMembers {
|
|
|
|
_, err := client.Organization.OrgAddTeamMember(organization.NewOrgAddTeamMemberParams().WithID(teamID).WithUsername(user.UserID), r.DefaultAuthentication)
|
|
|
|
if err != nil {
|
2025-03-01 00:26:08 +01:00
|
|
|
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)
|
|
|
|
}
|
2025-02-28 17:36:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-01 00:26:08 +01:00
|
|
|
if DebugMode {
|
|
|
|
log.Println("org team synced", groupName)
|
|
|
|
}
|
|
|
|
|
|
|
|
return missing
|
2025-02-28 17:36:35 +01:00
|
|
|
}
|
|
|
|
|
2025-03-01 00:26:08 +01:00
|
|
|
func syncOrgOwners(uids []common.PersonRepoMeta) []string {
|
2025-03-03 15:03:43 +01:00
|
|
|
return syncOrgTeams("Owners", append(uids, common.PersonRepoMeta{UserID: "autogits-devel"}))
|
2025-02-28 17:36:35 +01:00
|
|
|
}
|
|
|
|
|
2025-03-01 00:26:08 +01:00
|
|
|
func syncPackageCollaborators(pkg string, uids []common.PersonRepoMeta) []string {
|
|
|
|
missing := []string{}
|
|
|
|
collab, err := client.Repository.RepoListCollaborators(repository.NewRepoListCollaboratorsParams().WithOwner(org).WithRepo(pkg), r.DefaultAuthentication)
|
|
|
|
if err != nil {
|
|
|
|
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
|
2025-02-28 17:36:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func syncMaintainersToGitea(pkgs []string) {
|
2025-03-03 15:03:43 +01:00
|
|
|
maintainers := common.MaintainershipMap{
|
|
|
|
Data: map[string][]string{},
|
|
|
|
}
|
|
|
|
|
2025-02-28 17:36:35 +01:00
|
|
|
prjMeta, err := obs.GetProjectMeta(prj)
|
|
|
|
if err != nil {
|
|
|
|
log.Panicln("failed to get project meta", prj, err)
|
|
|
|
}
|
|
|
|
|
2025-03-01 00:26:08 +01:00
|
|
|
missingDevs := []string{}
|
|
|
|
|
2025-02-28 17:36:35 +01:00
|
|
|
for _, group := range prjMeta.Groups {
|
|
|
|
teamMembers, err := obs.GetGroupMeta(group.GroupID)
|
|
|
|
if err != nil {
|
|
|
|
log.Panicln("failed to get group", err)
|
|
|
|
}
|
|
|
|
|
2025-03-01 00:26:08 +01:00
|
|
|
if DebugMode {
|
|
|
|
log.Println("syncing", group.GroupID, teamMembers.Persons)
|
|
|
|
}
|
|
|
|
missingDevs = append(missingDevs, syncOrgTeams(group.GroupID, teamMembers.Persons.Persons)...)
|
2025-03-03 15:03:43 +01:00
|
|
|
devs := []string{}
|
|
|
|
for _, m := range teamMembers.Persons.Persons {
|
|
|
|
devs = append(devs, m.UserID)
|
|
|
|
}
|
|
|
|
maintainers.Data[""] = devs
|
2025-02-28 17:36:35 +01:00
|
|
|
}
|
2025-03-01 00:26:08 +01:00
|
|
|
missingDevs = append(missingDevs, syncOrgOwners(prjMeta.Persons)...)
|
2025-02-28 17:36:35 +01:00
|
|
|
|
|
|
|
for _, pkg := range pkgs {
|
|
|
|
pkgMeta, err := obs.GetPackageMeta(prj, pkg)
|
|
|
|
if err != nil {
|
|
|
|
log.Panicln("failed to get package meta", prj, pkg, err)
|
|
|
|
}
|
2025-03-01 00:26:08 +01:00
|
|
|
missingDevs = append(missingDevs, syncPackageCollaborators(pkg, pkgMeta.Persons)...)
|
2025-03-03 15:03:43 +01:00
|
|
|
|
|
|
|
devs := []string{}
|
|
|
|
for _, m := range pkgMeta.Persons {
|
|
|
|
devs = append(devs, m.UserID)
|
|
|
|
}
|
|
|
|
maintainers.Data[pkg] = devs
|
2025-02-28 17:36:35 +01:00
|
|
|
}
|
|
|
|
|
2025-03-01 00:26:08 +01:00
|
|
|
slices.Sort(missingDevs)
|
2025-03-03 15:03:43 +01:00
|
|
|
file, err := os.Create(common.MaintainershipFile)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(" *** Cannot create maintainership file:", err)
|
|
|
|
} else {
|
|
|
|
maintainers.WriteMaintainershipFile(file)
|
|
|
|
file.Close()
|
|
|
|
}
|
2025-03-01 00:26:08 +01:00
|
|
|
log.Println("Users without Gitea accounts:", slices.Compact(missingDevs))
|
2025-02-28 17:36:35 +01:00
|
|
|
}
|
|
|
|
|
2025-03-01 00:26:08 +01:00
|
|
|
var client *apiclient.GiteaAPI
|
2025-02-28 17:36:35 +01:00
|
|
|
var r *transport.Runtime
|
|
|
|
var git common.Git
|
|
|
|
var obs *common.ObsClient
|
|
|
|
var prj, org string
|
|
|
|
|
|
|
|
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")
|
|
|
|
}
|
2025-02-26 18:10:42 +01:00
|
|
|
|
2025-02-28 17:36:35 +01:00
|
|
|
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")
|
|
|
|
//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("maintainer-only", false, "Get maintainers only and exit")
|
|
|
|
syncMaintainers := flags.Bool("sync-maintainers-only", false, "Sync maintainers to Gitea and exit")
|
|
|
|
|
|
|
|
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)
|
2025-03-01 00:26:08 +01:00
|
|
|
client = apiclient.New(r, nil)
|
2025-02-28 17:36:35 +01:00
|
|
|
|
|
|
|
obs, _ = common.NewObsClient("api.opensuse.org")
|
|
|
|
|
|
|
|
gh := common.GitHandlerGeneratorImpl{}
|
|
|
|
var err error
|
|
|
|
git, err = gh.CreateGitHandler("Autogits - Devel Importer", "not.exist", "devel-importer")
|
|
|
|
if err != nil {
|
|
|
|
log.Panicln("Failed to allocate git handler. Err:", err)
|
|
|
|
}
|
|
|
|
if DebugMode {
|
|
|
|
if len(*debugGitPath) > 0 {
|
|
|
|
git.Close()
|
|
|
|
git, err = gh.ReadExistingPath("Autogits - Devel Importer", "not.exist", *debugGitPath)
|
|
|
|
if err != nil {
|
|
|
|
log.Panicln(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Println(" - working directory:" + git.GetPath())
|
|
|
|
} else {
|
|
|
|
defer git.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
prj = flags.Arg(0)
|
|
|
|
org = flags.Arg(1)
|
|
|
|
packages, err := runObsCommand("ls", prj)
|
|
|
|
|
2025-03-01 00:26:08 +01:00
|
|
|
/*
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2025-02-28 17:36:35 +01:00
|
|
|
if *getMaintainers {
|
|
|
|
listMaintainers(obs, prj, packages)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if *syncMaintainers {
|
2025-03-01 00:26:08 +01:00
|
|
|
syncMaintainersToGitea(packages)
|
2025-02-28 17:36:35 +01:00
|
|
|
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)
|
|
|
|
}
|
2025-02-26 18:10:42 +01:00
|
|
|
|
2025-03-01 00:26:08 +01:00
|
|
|
importRepos(packages)
|
|
|
|
syncMaintainersToGitea(packages)
|
2024-08-07 01:37:55 +02:00
|
|
|
}
|