13 Commits

Author SHA256 Message Date
52e9b9931c staging: Fix error message of missing staging.config
Some checks failed
go-generate-check / go-generate-check (pull_request) Successful in 9s
Integration tests / t (pull_request) Failing after 6m8s
2026-03-04 11:17:25 +01:00
783c676ad0 obs-staging-bot: Temporary hack for current factory setup
All checks were successful
go-generate-check / go-generate-check (pull_request) Successful in 8s
Integration tests / t (pull_request) Successful in 5m53s
We accept currently the temporary openSUSE:Factory:git not being the
master of openSUSE:Factory:PullRequest. We want to have it at the final
place. Once factory switches to git, content of openSUSE:Factory:git
will move to openSUSE:Factory and we can drop this exception again
2026-03-03 15:56:49 +01:00
d0c2c788c4 obs: Adding new linkedbuild mode
Some checks failed
go-generate-check / go-generate-check (pull_request) Successful in 9s
Integration tests / t (pull_request) Failing after 5m57s
2026-03-03 15:29:05 +01:00
3112ed7f97 Use our IBS branch for packaging
Some checks failed
go-generate-check / go-generate-check (pull_request) Successful in 38s
Integration tests / t (pull_request) Failing after 9m40s
Need to use it from internal resource policy wise, but will be mirrored outside.
2026-02-26 11:12:30 +01:00
4dd54b5db1 staging: Hardcode currently used user of IBS
We should consolidate to a nicer user name...
2026-02-26 11:12:29 +01:00
dde0e04a03 Always handle build results as building when dirty flag is set 2026-02-26 11:12:29 +01:00
346c6d836a staging: Fix service file to work with obs ssh connections
We need a fixed user for that
2026-02-26 11:12:29 +01:00
cc37fa73bd Add an "lfs fsck" check after submodule update
to avoid merging pull requests where lfs objects are not correctly
registered. Can happen when user has not installed lfs for example.
2026-02-26 11:12:29 +01:00
7740021192 Disable temporary comment adding in case of lacking permissions 2026-02-26 11:12:29 +01:00
6b76c3efc0 pr: only update PR if elided title not changed
Gitea trims long titles so we need to compare if the trimmed length
is same, not entire string that will always differ.
2026-02-26 11:12:29 +01:00
d04b67fde7 Support remote source in pull requests
This requires write permission by maintainer there
2026-02-26 11:12:29 +01:00
93812e34d9 workflow-direct: use relative path when adding a submodule
This solves the issue of using the right credentials based on the
main repo.
Also it allows to rename the organisation.
2026-02-26 11:12:29 +01:00
439d1baf64 Fix build status check when package source is in subdir
We must not use the upper directory name as part of the package name.
2026-02-26 11:12:29 +01:00
7 changed files with 76 additions and 26 deletions

View File

@@ -22,6 +22,8 @@ Release: 0
Summary: GitWorkflow utilities Summary: GitWorkflow utilities
License: GPL-2.0-or-later License: GPL-2.0-or-later
URL: https://src.opensuse.org/adamm/autogits URL: https://src.opensuse.org/adamm/autogits
#!RemoteAsset: git+https://src.suse.de/adrianSuSE/autogits#ibs_state
Source0: %name-%version.tar.xz
BuildRequires: git BuildRequires: git
BuildRequires: systemd-rpm-macros BuildRequires: systemd-rpm-macros
BuildRequires: go BuildRequires: go

View File

@@ -288,7 +288,7 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error
func (e *GitHandlerImpl) GitBranchHead(gitDir, branchName string) (string, error) { func (e *GitHandlerImpl) GitBranchHead(gitDir, branchName string) (string, error) {
id, err := e.GitExecWithOutput(gitDir, "show-ref", "--heads", "--hash", branchName) id, err := e.GitExecWithOutput(gitDir, "show-ref", "--heads", "--hash", branchName)
if err != nil { if err != nil {
return "", fmt.Errorf("Can't find default branch: %s", branchName) return "", fmt.Errorf("Can't find default branch: %s in %s", branchName, gitDir)
} }
id = strings.TrimSpace(SplitLines(id)[0]) id = strings.TrimSpace(SplitLines(id)[0])
@@ -302,7 +302,7 @@ func (e *GitHandlerImpl) GitBranchHead(gitDir, branchName string) (string, error
func (e *GitHandlerImpl) GitRemoteHead(gitDir, remote, branchName string) (string, error) { func (e *GitHandlerImpl) GitRemoteHead(gitDir, remote, branchName string) (string, error) {
id, err := e.GitExecWithOutput(gitDir, "show-ref", "--hash", "--verify", "refs/remotes/"+remote+"/"+branchName) id, err := e.GitExecWithOutput(gitDir, "show-ref", "--hash", "--verify", "refs/remotes/"+remote+"/"+branchName)
if err != nil { if err != nil {
return "", fmt.Errorf("Can't find default branch: %s", branchName) return "", fmt.Errorf("Can't find default branch: %s in %s", branchName, gitDir)
} }
return strings.TrimSpace(id), nil return strings.TrimSpace(id), nil

View File

@@ -520,7 +520,7 @@ func ObsSafeProjectName(prjname string) string {
} }
var ValidBlockModes []string = []string{"all", "local", "never"} var ValidBlockModes []string = []string{"all", "local", "never"}
var ValidPrjLinkModes []string = []string{"off", "localdep", "alldirect", "all"} var ValidPrjLinkModes []string = []string{"off", "localdep", "alldirect", "alldirect_or_localdep", "all"}
var ValidTriggerModes []string = []string{"transitive", "direct", "local"} var ValidTriggerModes []string = []string{"transitive", "direct", "local"}
func (c *ObsClient) SetProjectMeta(meta *ProjectMeta) error { func (c *ObsClient) SetProjectMeta(meta *ProjectMeta) error {
@@ -694,13 +694,15 @@ func (r *BuildResultList) BuildResultSummary() (success, finished bool) {
if !ok { if !ok {
panic("Unknown result code: " + result.Code) panic("Unknown result code: " + result.Code)
} }
if r.isLastBuild && result.Code == "unknown" { if r.isLastBuild {
// it means the package has never build yet, // we are always finished, since it is the last result
// but we don't know the reason // also when there is "unknown" state, it just means it
detail.Finished = true // it was never done
finished = true
} else {
finished = finished && detail.Finished
} }
finished = finished && detail.Finished
success = success && detail.Success success = success && detail.Success
if !finished { if !finished {

View File

@@ -27,6 +27,7 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath"
"regexp" "regexp"
"runtime/debug" "runtime/debug"
"slices" "slices"
@@ -127,6 +128,10 @@ func ProcessBuildStatus(project *common.BuildResultList) BuildStatusSummary {
found: found:
for j := 0; j < len(project.Result); j++ { for j := 0; j < len(project.Result); j++ {
common.LogDebug(" found match for @ idx:", j) common.LogDebug(" found match for @ idx:", j)
if project.Result[i].Dirty {
// ignore possible temporary failures and wait for settling
return BuildStatusSummaryBuilding
}
res := ProcessRepoBuildStatus(project.Result[i].Status) res := ProcessRepoBuildStatus(project.Result[i].Status)
switch res { switch res {
case BuildStatusSummarySuccess: case BuildStatusSummarySuccess:
@@ -797,7 +802,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
if err != nil { if err != nil {
common.LogError("Staging config", common.StagingConfigFile, "not found in PR to the project. Aborting.") common.LogError("Staging config", common.StagingConfigFile, "not found in PR to the project. Aborting.")
if !IsDryRun { if !IsDryRun {
_, err = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find project config in PR: "+common.ProjectConfigFile) _, err = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find staging config in PR ("+common.StagingConfigFile+")")
} }
return true, err return true, err
} }
@@ -856,8 +861,11 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
// NOTE: this is user input, so we need some limits here // NOTE: this is user input, so we need some limits here
l := len(stagingConfig.ObsProject) l := len(stagingConfig.ObsProject)
if l >= len(stagingConfig.StagingProject) || stagingConfig.ObsProject != stagingConfig.StagingProject[0:l] { if l >= len(stagingConfig.StagingProject) || stagingConfig.ObsProject != stagingConfig.StagingProject[0:l] {
common.LogError("StagingProject (", stagingConfig.StagingProject, ") is not child of target project", stagingConfig.ObsProject) // TEMPORARY HACK: We remove this when Factory has switched to git
return true, nil if ( stagingConfig.ObsProject != "openSUSE:Factory:git" && stagingConfig.StagingProject != "openSUSE:Factory:PullRequest" ) {
common.LogError("StagingProject (", stagingConfig.StagingProject, ") is not child of target project", stagingConfig.ObsProject)
return true, nil
}
} }
} }
@@ -886,10 +894,13 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
if !stagingConfig.RebuildAll { if !stagingConfig.RebuildAll {
for pkg, headOid := range headSubmodules { for pkg, headOid := range headSubmodules {
if baseOid, exists := baseSubmodules[pkg]; !exists || baseOid != headOid { if baseOid, exists := baseSubmodules[pkg]; !exists || baseOid != headOid {
if pkg != "rpms" && pkg != "dependencies" {
_, spkg := filepath.Split(pkg)
if exists { if exists {
modifiedPackages = append(modifiedPackages, pkg) modifiedPackages = append(modifiedPackages, spkg)
} else { } else {
newPackages = append(newPackages, pkg) newPackages = append(newPackages, spkg)
}
} }
common.LogDebug(pkg, ":", baseOid, "->", headOid) common.LogDebug(pkg, ":", baseOid, "->", headOid)
} }
@@ -1018,6 +1029,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
} }
} }
} }
switch overallBuildStatus { switch overallBuildStatus {
case BuildStatusSummarySuccess: case BuildStatusSummarySuccess:
status.Status = common.CommitStatus_Success status.Status = common.CommitStatus_Success

View File

@@ -6,9 +6,13 @@ After=network-online.target
Type=exec Type=exec
ExecStart=/usr/bin/obs-staging-bot ExecStart=/usr/bin/obs-staging-bot
EnvironmentFile=-/etc/default/obs-staging-bot.env EnvironmentFile=-/etc/default/obs-staging-bot.env
DynamicUser=yes User=autogits_obs_staging_bot
NoNewPrivileges=yes Group=users
ProtectSystem=strict
# This may work when not using ssh api connections:
#DynamicUser=yes
#NoNewPrivileges=yes
#ProtectSystem=strict
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -123,7 +123,7 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
common.LogError(" - ", action.Repository.Name, "repo is not sha256. Ignoring.") common.LogError(" - ", action.Repository.Name, "repo is not sha256. Ignoring.")
return return
} }
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name)) common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + action.Repository.Name, action.Repository.Name))
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f") defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current")) branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current"))
@@ -215,7 +215,7 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common
} }
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil { if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil {
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name) git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + action.Repository.Name, action.Repository.Name)
common.LogDebug("Pushed to package that is not part of the project. Re-adding...", err) common.LogDebug("Pushed to package that is not part of the project. Re-adding...", err)
} else if !stat.IsDir() { } else if !stat.IsDir() {
common.LogError("Pushed to a package that is not a submodule but exists in the project. Ignoring.") common.LogError("Pushed to a package that is not a submodule but exists in the project. Ignoring.")
@@ -420,7 +420,7 @@ next_repo:
} }
// add repository to git project // add repository to git project
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", r.CloneURL, r.Name)) common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + r.Name, r.Name))
curBranch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current")) curBranch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
if branch != curBranch { if branch != curBranch {

View File

@@ -180,6 +180,33 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
} }
updateSubmoduleInPR(submodulePath, prHead, git) updateSubmoduleInPR(submodulePath, prHead, git)
err := git.GitExec(path.Join(common.DefaultGitPrj, submodulePath), "lfs", "fetch")
common.LogError("lfs fetch err: ", err)
if err = git.GitExec(path.Join(common.DefaultGitPrj, submodulePath), "lfs", "fsck"); err != nil {
found_comment := false
timeline, terr := common.FetchTimelineSinceLastPush(Gitea, prHead, org, repo, idx)
if terr != nil {
common.LogError("lfs fsck error, but timeline fetch failed")
break
}
msgPrefix := "The LFS objects are broken!"
for _, t := range timeline {
if t.Type == common.TimelineCommentType_Comment && strings.HasPrefix(t.Body, msgPrefix) {
found_comment = true
common.LogError("lfs fsck Comment already found")
break
}
}
if !found_comment && !common.IsDryRun {
Gitea.AddComment(pr.PR, msgPrefix + " Please verify with 'git lfs fsck'")
}
common.LogError("lfs fsck failed with: ", err.Error())
return err
}
status, err := git.GitStatus(common.DefaultGitPrj) status, err := git.GitStatus(common.DefaultGitPrj)
common.LogDebug("status:", status) common.LogDebug("status:", status)
common.LogDebug("submodule", repo, " hash:", id, " -> ", prHead) common.LogDebug("submodule", repo, " hash:", id, " -> ", prHead)
@@ -309,14 +336,16 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
PrjGit := PrjGitPR.PR.Base.Repo PrjGit := PrjGitPR.PR.Base.Repo
prjGitPRbranch := PrjGitPR.PR.Head.Name prjGitPRbranch := PrjGitPR.PR.Head.Name
if PrjGitPR.PR.Base.RepoID != PrjGitPR.PR.Head.RepoID { if PrjGitPR.PR.Base.RepoID != PrjGitPR.PR.Head.RepoID {
PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, "", PrjGit.SSHURL) // permission check, if submission comes from foreign repo
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", PrjGitPR.RemoteName, PrjGitPR.PR.Head.Sha) if !PrjGitPR.PR.AllowMaintainerEdit {
git.GitExecOrPanic(common.DefaultGitPrj, "checkout", PrjGitPR.PR.Head.Sha) common.LogError("Warning: source and target branch are in different repositories. We may not have the right permissions...")
common.LogInfo("Cannot update this PR as it's on another remote, not branch:", prjGitPRbranch, "Assuming this is by-design. (eg. project git PR only)") // Gitea.AddComment(PrjGitPR.PR, "This PR does not allow maintainer changes, but referenced package branch has changed!")
return nil return nil
}
} }
PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, prjGitPRbranch, PrjGit.SSHURL) // PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, prjGitPRbranch, PrjGit.SSHURL)
PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, PrjGitPR.PR.Head.Ref, PrjGitPR.PR.Head.Repo.SSHURL)
common.PanicOnError(err) common.PanicOnError(err)
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", PrjGitPR.RemoteName, PrjGitBranch) git.GitExecOrPanic(common.DefaultGitPrj, "fetch", PrjGitPR.RemoteName, PrjGitBranch)
@@ -364,6 +393,7 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
} }
common.PanicOnError(git.GitExec(common.DefaultGitPrj, params...)) common.PanicOnError(git.GitExec(common.DefaultGitPrj, params...))
PrjGitPR.PR.Head.Sha = newHeadCommit PrjGitPR.PR.Head.Sha = newHeadCommit
Gitea.SetLabels(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, []string{prset.Config.Label("PR/updated")})
} }
// update PR // update PR
@@ -380,7 +410,7 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
} }
return CurrentTitle == NewTitle return CurrentTitle == NewTitle
} }
if PrjGitPR.PR.User.UserName == CurrentUser.UserName && (PrjGitPR.PR.Body != PrjGitBody || !isPrTitleSame(PrjGitPR.PR.Title, PrjGitTitle)) { if !pr.config.NoProjectGitPR && PrjGitPR.PR.User.UserName == CurrentUser.UserName && (PrjGitPR.PR.Body != PrjGitBody || !isPrTitleSame(PrjGitPR.PR.Title, PrjGitTitle)) {
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, &models.EditPullRequestOption{ Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, &models.EditPullRequestOption{
RemoveDeadline: true, RemoveDeadline: true,
Title: PrjGitTitle, Title: PrjGitTitle,