diff --git a/common/config.go b/common/config.go index 7e61fef..43a9c83 100644 --- a/common/config.go +++ b/common/config.go @@ -55,6 +55,8 @@ type QAConfig struct { Name string Origin string BuildDisableRepos []string // which repos to build disable in the new project + ForkProject bool // whether to fork the Origin project and use that as scmsync + ForkOrganization string // which organization to use when forking } type Permissions struct { diff --git a/common/utils.go b/common/utils.go index c907c8a..7b20258 100644 --- a/common/utils.go +++ b/common/utils.go @@ -286,3 +286,13 @@ func TrimRemovedBranchSuffix(branchName string) string { return branchName } + +func Slugify(orig string) string { + return strings.Map(func(r rune) rune { + if unicode.IsLetter(r) || unicode.IsDigit(r) { + return unicode.ToLower(r) + } + + return '-' + }, orig) +} \ No newline at end of file diff --git a/obs-staging-bot/README.md b/obs-staging-bot/README.md index ff20c0e..0727813 100644 --- a/obs-staging-bot/README.md +++ b/obs-staging-bot/README.md @@ -35,7 +35,9 @@ It's a JSON file with following syntax: { "Name": "SLES", "Origin": "SUSE:SLFO:Products:SLES:16.0", - "BuildDisableRepos": ["product"] + "BuildDisableRepos": ["product"], + "ForkProject": false, + "ForkOrganization": "products-staging" } ] } @@ -49,6 +51,8 @@ It's a JSON file with following syntax: | *QA > Name* | Suffix for the QA OBS staging project. The project is named *StagingProject::Name*. | no | string | | | | *QA > Origin* | OBS reference project | no | string | | | | *QA > BuildDisableRepos* | The names of OBS repositories to build-disable, if any. | no | array of strings | | [] | +| *QA > ForkProject* | Whether to fork the project | no | boolean | true/false | false | +| *QA > ForkOrganization* | The organization where the fork project resiedes | yes (if using ForkProject) | string | `[a-zA-Z0-9-_:]+` | | Details @@ -67,6 +71,8 @@ Details * **PrjGit PR - QA staging project** * The QA staging project is meant for building the product; the relative build config is inherited from the `QA > Origin` project. * In this case, the **scmsync** tag is inherited from the `QA > Origin` project. - * It is desirable in some cases to avoid building some specific build service repositories when not needed. In this case, `QA > BuildDisableRepos` can be specified. + * It is desirable in some cases to avoid building some specific build service repositories when not needed. In this case, `QA > BuildDisableRepos` can be specified. These repositories would be disabled in the project meta when generating the QA project. - + * When using the **ForkProject** setting, the current ref specified in the `scmsync` definition in the `QA > Origin` project's meta is pushed to a separate git project, + *`QA > ForkOrganization/QA > Name`*. `QA > ForkOrganization` must be specified in this case. + The target project and organization must already exist. diff --git a/obs-staging-bot/main.go b/obs-staging-bot/main.go index ccd13c6..bd3ee73 100644 --- a/obs-staging-bot/main.go +++ b/obs-staging-bot/main.go @@ -376,13 +376,14 @@ func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullReque return meta, nil } + // buildProject // ^- templateProject // // stagingProject:$buildProject // ^- stagingProject:$buildProject:$subProjectName (based on templateProject) -func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject, templateProject, subProjectName string, buildDisableRepos []string) error { +func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject, templateProject, subProjectName string, buildDisableRepos []string, forkProject bool, forkOrganization string) error { common.LogDebug("Setup QA sub projects") templateMeta, err := ObsClient.GetProjectMeta(templateProject) if err != nil { @@ -408,8 +409,54 @@ func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, git panic(err) } - // set expanded commit url - repository.Fragment = branch.SHA + if forkProject && len(forkOrganization) > 0 { + // Clone the original repository + origin_repo, err := gitea.GetRepository(org, repo) + if err != nil { + common.LogError("unable to get original repository details: ", org, repo, err) + return err + } + RemoteName, err := git.GitClone(subProjectName, repository.Fragment, origin_repo.SSHURL) + if err != nil { + common.LogError("unable to clone original repository: ", err) + return err + } + err = git.GitExec(subProjectName, "fetch", "--prune", RemoteName, branch.SHA) + if err != nil { + common.LogError("unable to fetch original repository: ", err) + return err + } + + // Push the repository to the forked remote. + // We don't need to change the content. + forked_branch_name := "QA_"+common.Slugify(stagingProject) + + if !IsDryRun { + forked_repo, err := gitea.GetRepository(forkOrganization, subProjectName) + if err != nil { + common.LogError("unable to get forked repository details: ", forkOrganization, subProjectName, err) + return err + } + + repository, err = url.Parse(forked_repo.CloneURL) + common.PanicOnError(err) + err = git.GitExec(subProjectName, "push", "--force", forked_repo.SSHURL, branch.SHA+":refs/heads/"+forked_branch_name) + if err != nil { + common.LogError("unable to push to the forked repository: ", err) + return err + } + } + + // use the branch name + repository.Fragment = forked_branch_name + } else { + if forkProject && len(forkOrganization) == 0 { + // this is probably a misconfiguration, so complain + common.LogError("ForkProject specified without a ForkOrganization - the Origin scmsync branch will be used instead") + } + // set expanded commit url + repository.Fragment = branch.SHA + } templateMeta.ScmSync = repository.String() common.LogDebug("Setting scmsync url to ", templateMeta.ScmSync) } @@ -942,7 +989,9 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e stagingProject, setup.Origin, setup.Name, - setup.BuildDisableRepos) + setup.BuildDisableRepos, + setup.ForkProject, + setup.ForkOrganization) msg = msg + ObsWebHost + "/project/show/" + stagingProject + ":" + setup.Name + "\n" }