From 93c970d0dd340c37b0d3e0f2d386e7e88d8178e61860bdd658d37a803032c294 Mon Sep 17 00:00:00 2001 From: Adam Majer Date: Mon, 3 Nov 2025 16:20:36 +0100 Subject: [PATCH 1/5] direct: move logging to common.Log* function --- workflow-direct/main.go | 191 +++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 112 deletions(-) diff --git a/workflow-direct/main.go b/workflow-direct/main.go index 4fe532c..4addf71 100644 --- a/workflow-direct/main.go +++ b/workflow-direct/main.go @@ -22,7 +22,6 @@ import ( "flag" "fmt" "io/fs" - "log" "math/rand" "net/url" "os" @@ -40,7 +39,7 @@ import ( const ( AppName = "direct_workflow" GitAuthor = "AutoGits prjgit-updater" - GitEmail = "adam+autogits-direct@zombino.com" + GitEmail = "autogits-direct@noreply@src.opensuse.org" ) var configuredRepos map[string][]*common.AutogitConfig @@ -53,18 +52,6 @@ func isConfiguredOrg(org *common.Organization) bool { return found } -func concatenateErrors(err1, err2 error) error { - if err1 == nil { - return err2 - } - - if err2 == nil { - return err1 - } - - return fmt.Errorf("%w\n%w", err1, err2) -} - type RepositoryActionProcessor struct{} func (*RepositoryActionProcessor) ProcessFunc(request *common.Request) error { @@ -72,26 +59,25 @@ func (*RepositoryActionProcessor) ProcessFunc(request *common.Request) error { configs, configFound := configuredRepos[action.Organization.Username] if !configFound { - log.Printf("Repository event for %s. Not configured. Ignoring.\n", action.Organization.Username) + common.LogInfo("Repository event for", action.Organization.Username, ". Not configured. Ignoring.", action.Organization.Username) return nil } for _, config := range configs { if org, repo, _ := config.GetPrjGit(); org == action.Repository.Owner.Username && repo == action.Repository.Name { - log.Println("+ ignoring repo event for PrjGit repository", config.GitProjectName) + common.LogError("+ ignoring repo event for PrjGit repository", config.GitProjectName) return nil } } - var err error for _, config := range configs { - err = concatenateErrors(err, processConfiguredRepositoryAction(action, config)) + processConfiguredRepositoryAction(action, config) } - return err + return nil } -func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, config *common.AutogitConfig) error { +func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, config *common.AutogitConfig) { gitOrg, gitPrj, gitBranch := config.GetPrjGit() git, err := gh.CreateGitHandler(config.Organization) common.PanicOnError(err) @@ -103,7 +89,8 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj) if err != nil { - return fmt.Errorf("Error accessing/creating prjgit: %s/%s#%s err: %w", gitOrg, gitPrj, gitBranch, err) + common.LogError("Error accessing/creating prjgit:", gitOrg, gitPrj, gitBranch, err) + return } remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL) @@ -112,29 +99,29 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co switch action.Action { case "created": if action.Repository.Object_Format_Name != "sha256" { - return fmt.Errorf(" - '%s' repo is not sha256. Ignoring.", action.Repository.Name) + common.LogError(" - '%s' repo is not sha256. Ignoring.", action.Repository.Name) + return } common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name)) - defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all") + defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all") branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current")) if branch != config.Branch { if err := git.GitExec(path.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "origin", config.Branch+":"+config.Branch); err != nil { - return fmt.Errorf("error fetching branch %s. ignoring as non-existent. err: %w", config.Branch, err) // no branch? so ignore repo here + common.LogError("error fetching branch", config.Branch, ". ignoring as non-existent.", err) // no branch? so ignore repo here + return } common.PanicOnError(git.GitExec(path.Join(gitPrj, action.Repository.Name), "checkout", config.Branch)) } - common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Automatic package inclusion via Direct Workflow")) + common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Auto-inclusion "+action.Repository.Name)) if !noop { common.PanicOnError(git.GitExec(gitPrj, "push")) } case "deleted": if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil || !stat.IsDir() { - if DebugMode { - log.Println("delete event for", action.Repository.Name, "-- not in project. Ignoring") - } - return nil + common.LogDebug("delete event for", action.Repository.Name, "-- not in project. Ignoring") + return } common.PanicOnError(git.GitExec(gitPrj, "rm", action.Repository.Name)) common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Automatic package removal via Direct Workflow")) @@ -143,10 +130,9 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co } default: - return fmt.Errorf("%s: %s", "Unknown action type", action.Action) + common.LogError("Unknown action type:", action.Action) + return } - - return nil } type PushActionProcessor struct{} @@ -156,40 +142,39 @@ func (*PushActionProcessor) ProcessFunc(request *common.Request) error { configs, configFound := configuredRepos[action.Repository.Owner.Username] if !configFound { - log.Printf("Repository event for %s. Not configured. Ignoring.\n", action.Repository.Owner.Username) + common.LogDebug("Repository event for", action.Repository.Owner.Username, ". Not configured. Ignoring.") return nil } for _, config := range configs { if gitOrg, gitPrj, _ := config.GetPrjGit(); gitOrg == action.Repository.Owner.Username && gitPrj == action.Repository.Name { - log.Println("+ ignoring push to PrjGit repository", config.GitProjectName) + common.LogInfo("+ ignoring push to PrjGit repository", config.GitProjectName) return nil } } - var err error for _, config := range configs { - err = concatenateErrors(err, processConfiguredPushAction(action, config)) + processConfiguredPushAction(action, config) } - - return err + return nil } -func processConfiguredPushAction(action *common.PushWebhookEvent, config *common.AutogitConfig) error { +func processConfiguredPushAction(action *common.PushWebhookEvent, config *common.AutogitConfig) { gitOrg, gitPrj, gitBranch := config.GetPrjGit() git, err := gh.CreateGitHandler(config.Organization) common.PanicOnError(err) defer git.Close() - log.Printf("push to: %s/%s for %s/%s#%s", action.Repository.Owner.Username, action.Repository.Name, gitOrg, gitPrj, gitBranch) + common.LogDebug("push to:", action.Repository.Owner.Username, action.Repository.Name, "for:", gitOrg, gitPrj, gitBranch) if len(config.Branch) == 0 { config.Branch = action.Repository.Default_Branch - log.Println(" + default branch", action.Repository.Default_Branch) + common.LogDebug(" + default branch", action.Repository.Default_Branch) } prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj) if err != nil { - return fmt.Errorf("Error accessing/creating prjgit: %s/%s err: %w", gitOrg, gitPrj, err) + common.LogError("Error accessing/creating prjgit:", gitOrg, gitPrj, err) + return } remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL) @@ -198,21 +183,20 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common common.PanicOnError(err) commit, ok := git.GitSubmoduleCommitId(gitPrj, action.Repository.Name, headCommitId) for ok && action.Head_Commit.Id == commit { - log.Println(" -- nothing to do, commit already in ProjectGit") - return nil + common.LogDebug(" -- nothing to do, commit already in ProjectGit") + return } - if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil || !stat.IsDir() { - if DebugMode { - log.Println("Pushed to package that is not part of the project. Ignoring:", err) - } - return nil + if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil || !stat.IsDir(){ + common.LogError("Pushed to a package that is not a submodule but exists in the project. Ignoring.") + return } git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--depth", "1", "--checkout", action.Repository.Name) - defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all") + defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all") if err := git.GitExec(filepath.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "--force", remoteName, config.Branch+":"+config.Branch); err != nil { - return fmt.Errorf("error fetching branch %s. ignoring as non-existent. err: %w", config.Branch, err) // no branch? so ignore repo here + common.LogError("Error fetching branch:", config.Branch, "Ignoring as non-existent.", err) + return } id, err := git.GitRemoteHead(filepath.Join(gitPrj, action.Repository.Name), remoteName, config.Branch) common.PanicOnError(err) @@ -222,11 +206,10 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common if !noop { git.GitExecOrPanic(gitPrj, "push", remoteName) } - return nil + return } - log.Println("push of refs not on the configured branch", config.Branch, ". ignoring.") - return nil + common.LogDebug("push of refs not on the configured branch", config.Branch, ". ignoring.") } func verifyProjectState(git common.Git, org string, config *common.AutogitConfig, configs []*common.AutogitConfig) (err error) { @@ -248,42 +231,41 @@ func verifyProjectState(git common.Git, org string, config *common.AutogitConfig remoteName, err := git.GitClone(gitPrj, gitBranch, repo.SSHURL) common.PanicOnError(err) - defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all") + defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all") - log.Println(" * Getting submodule list") + common.LogDebug(" * Getting submodule list") sub, err := git.GitSubmoduleList(gitPrj, "HEAD") common.PanicOnError(err) - log.Println(" * Getting package links") + common.LogDebug(" * Getting package links") var pkgLinks []*PackageRebaseLink if f, err := fs.Stat(os.DirFS(path.Join(git.GetPath(), gitPrj)), common.PrjLinksFile); err == nil && (f.Mode()&fs.ModeType == 0) && f.Size() < 1000000 { if data, err := os.ReadFile(path.Join(git.GetPath(), gitPrj, common.PrjLinksFile)); err == nil { pkgLinks, err = parseProjectLinks(data) if err != nil { - log.Println("Cannot parse project links file:", err.Error()) + common.LogError("Cannot parse project links file:", err.Error()) pkgLinks = nil } else { ResolveLinks(org, pkgLinks, gitea) } } } else { - log.Println(" - No package links defined") + common.LogInfo(" - No package links defined") } /* Check existing submodule that they are updated */ - isGitUpdated := false next_package: for filename, commitId := range sub { // ignore project gits //for _, c := range configs { if gitPrj == filename { - log.Println(" prjgit as package? ignoring project git:", filename) + common.LogDebug(" prjgit as package? ignoring project git:", filename) continue next_package } //} - log.Printf(" verifying package: %s -> %s(%s)", commitId, filename, config.Branch) + common.LogDebug(" verifying package: %s -> %s(%s)", commitId, filename, config.Branch) commits, err := gitea.GetRecentCommits(org, filename, config.Branch, 10) if len(commits) == 0 { if repo, err := gitea.GetRepository(org, filename); repo == nil && err == nil { @@ -292,7 +274,7 @@ next_package: } } if err != nil { - log.Println(" -> failed to fetch recent commits for package:", filename, " Err:", err) + common.LogDebug(" -> failed to fetch recent commits for package:", filename, " Err:", err) continue } @@ -309,7 +291,7 @@ next_package: if l.Pkg == filename { link = l - log.Println(" -> linked package") + common.LogDebug(" -> linked package") // so, we need to rebase here. Can't really optimize, so clone entire package tree and remote pkgPath := path.Join(gitPrj, filename) git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--checkout", filename) @@ -340,42 +322,27 @@ next_package: common.PanicOnError(git.GitExec(gitPrj, "submodule", "update", "--init", "--depth", "1", "--checkout", filename)) common.PanicOnError(git.GitExec(filepath.Join(gitPrj, filename), "fetch", "--depth", "1", "origin", commits[0].SHA)) common.PanicOnError(git.GitExec(filepath.Join(gitPrj, filename), "checkout", commits[0].SHA)) - log.Println(" -> updated to", commits[0].SHA) + common.LogDebug(" -> updated to", commits[0].SHA) isGitUpdated = true } else { // probably need `merge-base` or `rev-list` here instead, or the project updated already - log.Println(" *** Cannot find SHA of last matching update for package:", filename, " Ignoring") + common.LogInfo(" *** Cannot find SHA of last matching update for package:", filename, " Ignoring") } } } // find all missing repositories, and add them - if DebugMode { - log.Println("checking for missing repositories...") - } + common.LogDebug("checking for missing repositories...") repos, err := gitea.GetOrganizationRepositories(org) if err != nil { return err } - if DebugMode { - log.Println(" nRepos:", len(repos)) - } + common.LogDebug(" nRepos:", len(repos)) /* Check repositories in org to make sure they are included in project git */ next_repo: for _, r := range repos { - if DebugMode { - log.Println(" -- checking", r.Name) - } - - if r.ObjectFormatName != "sha256" { - if DebugMode { - log.Println(" + ", r.ObjectFormatName, ". Needs to be sha256. Ignoring") - } - continue next_repo - } - // for _, c := range configs { if gitPrj == r.Name { // ignore project gits @@ -390,9 +357,7 @@ next_repo: } } - if DebugMode { - log.Println(" -- checking repository:", r.Name) - } + common.LogDebug(" -- checking repository:", r.Name) if _, err := gitea.GetRecentCommits(org, r.Name, config.Branch, 1); err != nil { // assumption that package does not exist, so not part of project @@ -423,10 +388,7 @@ next_repo: } } - if DebugMode { - log.Println("Verification finished for ", org, ", prjgit:", config.GitProjectName) - } - + common.LogInfo("Verification finished for ", org, ", prjgit:", config.GitProjectName) return nil } @@ -437,17 +399,17 @@ var checkInterval time.Duration func checkOrg(org string, configs []*common.AutogitConfig) { git, err := gh.CreateGitHandler(org) if err != nil { - log.Println("Faield to allocate GitHandler:", err) + common.LogError("Failed to allocate GitHandler:", err) return } defer git.Close() for _, config := range configs { - log.Printf(" ++ starting verification, org: `%s` config: `%s`\n", org, config.GitProjectName) + common.LogInfo(" ++ starting verification, org:", org, "config:", config.GitProjectName) if err := verifyProjectState(git, org, config, configs); err != nil { - log.Printf(" *** verification failed, org: `%s`, err: %#v\n", org, err) + common.LogError(" *** verification failed, org:", org, err) } else { - log.Printf(" ++ verification complete, org: `%s` config: `%s`\n", org, config.GitProjectName) + common.LogError(" ++ verification complete, org:", org, config.GitProjectName) } } } @@ -456,7 +418,7 @@ func checkRepos() { for org, configs := range configuredRepos { if checkInterval > 0 { sleepInterval := checkInterval - checkInterval/2 + time.Duration(rand.Int63n(int64(checkInterval))) - log.Println(" - sleep interval", sleepInterval, "until next check") + common.LogInfo(" - sleep interval", sleepInterval, "until next check") time.Sleep(sleepInterval) } @@ -468,9 +430,9 @@ func consistencyCheckProcess() { if checkOnStart { savedCheckInterval := checkInterval checkInterval = 0 - log.Println("== Startup consistency check begin...") + common.LogInfo("== Startup consistency check begin...") checkRepos() - log.Println("== Startup consistency check done...") + common.LogInfo("== Startup consistency check done...") checkInterval = savedCheckInterval } @@ -485,7 +447,8 @@ var gh common.GitHandlerGenerator func updateConfiguration(configFilename string, orgs *[]string) { configFile, err := common.ReadConfigFile(configFilename) if err != nil { - log.Fatal(err) + common.LogError(err) + os.Exit(4) } configs, _ := common.ResolveWorkflowConfigs(gitea, configFile) @@ -493,9 +456,7 @@ func updateConfiguration(configFilename string, orgs *[]string) { *orgs = make([]string, 0, 1) for _, c := range configs { if slices.Contains(c.Workflows, "direct") { - if DebugMode { - log.Printf(" + adding org: '%s', branch: '%s', prjgit: '%s'\n", c.Organization, c.Branch, c.GitProjectName) - } + common.LogDebug(" + adding org:", c.Organization, ", branch:", c.Branch, ", prjgit:", c.GitProjectName) configs := configuredRepos[c.Organization] if configs == nil { configs = make([]*common.AutogitConfig, 0, 1) @@ -520,10 +481,12 @@ func main() { flag.Parse() if err := common.RequireGiteaSecretToken(); err != nil { - log.Fatal(err) + common.LogError(err) + os.Exit(1) } if err := common.RequireRabbitSecrets(); err != nil { - log.Fatal(err) + common.LogError(err) + os.Exit(1) } defs := &common.RabbitMQGiteaEventsProcessor{} @@ -532,12 +495,14 @@ func main() { if len(*basePath) == 0 { *basePath, err = os.MkdirTemp(os.TempDir(), AppName) if err != nil { - log.Fatal(err) + common.LogError(err) + os.Exit(1) } } gh, err = common.AllocateGitWorkTree(*basePath, GitAuthor, GitEmail) if err != nil { - log.Fatal(err) + common.LogError(err) + os.Exit(1) } // handle reconfiguration @@ -552,10 +517,10 @@ func main() { } if sig != syscall.SIGHUP { - log.Println("Unexpected signal received:", sig) + common.LogError("Unexpected signal received:", sig) continue } - log.Println("*** Reconfiguring ***") + common.LogError("*** Reconfiguring ***") updateConfiguration(*configFilename, &defs.Orgs) defs.Connection().UpdateTopics(defs) } @@ -567,23 +532,25 @@ func main() { gitea = common.AllocateGiteaTransport(*giteaUrl) CurrentUser, err := gitea.GetCurrentUser() if err != nil { - log.Fatalln("Cannot fetch current user:", err) + common.LogError("Cannot fetch current user:", err) + os.Exit(2) } - log.Println("Current User:", CurrentUser.UserName) + common.LogInfo("Current User:", CurrentUser.UserName) updateConfiguration(*configFilename, &defs.Orgs) defs.Connection().RabbitURL, err = url.Parse(*rabbitUrl) if err != nil { - log.Panicf("cannot parse server URL. Err: %#v\n", err) + common.LogError("cannot parse server URL. Err:", err) + os.Exit(3) } go consistencyCheckProcess() - log.Println("defs:", *defs) + common.LogInfo("defs:", *defs) defs.Handlers = make(map[string]common.RequestProcessor) defs.Handlers[common.RequestType_Push] = &PushActionProcessor{} defs.Handlers[common.RequestType_Repository] = &RepositoryActionProcessor{} - log.Fatal(common.ProcessRabbitMQEvents(defs)) + common.LogError(common.ProcessRabbitMQEvents(defs)) } -- 2.51.1 From 736769d630e2b58a55dd58f37e80949578335f45458cb2eb085f174b7247ad95 Mon Sep 17 00:00:00 2001 From: Adam Majer Date: Mon, 3 Nov 2025 16:21:15 +0100 Subject: [PATCH 2/5] direct: add a repo with branch but no submodule --- workflow-direct/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/workflow-direct/main.go b/workflow-direct/main.go index 4addf71..81f1d38 100644 --- a/workflow-direct/main.go +++ b/workflow-direct/main.go @@ -187,7 +187,10 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common return } - if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil || !stat.IsDir(){ + if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil { + git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name) + common.LogDebug("Pushed to package that is not part of the project. Re-adding...", err) + } else if !stat.IsDir() { common.LogError("Pushed to a package that is not a submodule but exists in the project. Ignoring.") return } -- 2.51.1 From 0e06ba5993e5316cca79441a67200f594cf926a2d9f57968326786fc52a6240e Mon Sep 17 00:00:00 2001 From: Adam Majer Date: Mon, 3 Nov 2025 17:39:11 +0100 Subject: [PATCH 3/5] common: classifying rm branches on name Branches with suffixes -rm -removed -deleted are now classified as removed. This is important in case project config refers to default branch names which must exist so we need to be able to classify such branches to either use them or ignore them --- common/utils.go | 33 +++++++++++++++++++++++++- common/utils_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/common/utils.go b/common/utils.go index a504b4c..c3dca76 100644 --- a/common/utils.go +++ b/common/utils.go @@ -168,9 +168,10 @@ func FetchDevelProjects() (DevelProjects, error) { } var DevelProjectNotFound = errors.New("Devel project not found") + func (d DevelProjects) GetDevelProject(pkg string) (string, error) { for _, item := range d { - if item.Package == pkg { + if item.Package == pkg { return item.Project, nil } } @@ -178,3 +179,33 @@ func (d DevelProjects) GetDevelProject(pkg string) (string, error) { return "", DevelProjectNotFound } +var removedBranchNameSuffixes []string = []string{ + "-rm", + "-removed", + "-deleted", +} + +func findRemovedBranchSuffix(branchName string) string { + branchName = strings.ToLower(branchName) + + for _, suffix := range removedBranchNameSuffixes { + if len(suffix) < len(branchName) && strings.HasSuffix(branchName, suffix) { + return suffix + } + } + + return "" +} + +func IsRemovedBranch(branchName string) bool { + return len(findRemovedBranchSuffix(branchName)) > 0 +} + +func TrimRemovedBranchSuffix(branchName string) string { + suffix := findRemovedBranchSuffix(branchName) + if len(suffix) > 0 { + return branchName[0 : len(branchName)-len(suffix)] + } + + return branchName +} diff --git a/common/utils_test.go b/common/utils_test.go index 38507ce..608387d 100644 --- a/common/utils_test.go +++ b/common/utils_test.go @@ -165,3 +165,58 @@ func TestRemoteName(t *testing.T) { }) } } + +func TestRemovedBranchName(t *testing.T) { + tests := []struct { + name string + branchName string + isRemoved bool + regularName string + }{ + { + name: "Empty branch", + }, + { + name: "Removed suffix only", + branchName: "-rm", + isRemoved: false, + regularName: "-rm", + }, + { + name: "Capital suffix", + branchName: "Foo-Rm", + isRemoved: true, + regularName: "Foo", + }, + { + name: "Other suffixes", + isRemoved: true, + branchName: "Goo-Rm-DeleteD", + regularName: "Goo-Rm", + }, + { + name: "Other suffixes", + isRemoved: true, + branchName: "main-REMOVED", + regularName: "main", + }, + { + name: "Not removed separator", + isRemoved: false, + branchName: "main;REMOVED", + regularName: "main;REMOVED", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if r := common.IsRemovedBranch(test.branchName); r != test.isRemoved { + t.Error("Expecting isRemoved:", test.isRemoved, "but received", r) + } + + if tn := common.TrimRemovedBranchSuffix(test.branchName); tn != test.regularName { + t.Error("Expected stripped branch name to be:", test.regularName, "but have:", tn) + } + }) + } +} -- 2.51.1 From 8db558891a74888e31ddaea5c4c40e2d7c131cc2fa61c379951a101108596cf4 Mon Sep 17 00:00:00 2001 From: Adam Majer Date: Mon, 3 Nov 2025 17:46:31 +0100 Subject: [PATCH 4/5] direct: remove config.Branch clobbering use our own copy of branch instead of writing it in the config. This should fix handling of default branches where the default branch differs between repositories. --- workflow-direct/main.go | 80 +++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/workflow-direct/main.go b/workflow-direct/main.go index 81f1d38..711ae7a 100644 --- a/workflow-direct/main.go +++ b/workflow-direct/main.go @@ -83,8 +83,13 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co common.PanicOnError(err) defer git.Close() - if len(config.Branch) == 0 { - config.Branch = action.Repository.Default_Branch + configBranch := config.Branch + if len(configBranch) == 0 { + configBranch = action.Repository.Default_Branch + if common.IsRemovedBranch(configBranch) { + common.LogDebug(" - default branch has deleted suffix. Skipping") + return + } } prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj) @@ -106,12 +111,12 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all") branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current")) - if branch != config.Branch { - if err := git.GitExec(path.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "origin", config.Branch+":"+config.Branch); err != nil { - common.LogError("error fetching branch", config.Branch, ". ignoring as non-existent.", err) // no branch? so ignore repo here + if branch != configBranch { + if err := git.GitExec(path.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "origin", configBranch+":"+configBranch); err != nil { + common.LogError("error fetching branch", configBranch, ". ignoring as non-existent.", err) // no branch? so ignore repo here return } - common.PanicOnError(git.GitExec(path.Join(gitPrj, action.Repository.Name), "checkout", config.Branch)) + common.PanicOnError(git.GitExec(path.Join(gitPrj, action.Repository.Name), "checkout", configBranch)) } common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Auto-inclusion "+action.Repository.Name)) if !noop { @@ -166,9 +171,14 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common defer git.Close() common.LogDebug("push to:", action.Repository.Owner.Username, action.Repository.Name, "for:", gitOrg, gitPrj, gitBranch) - if len(config.Branch) == 0 { - config.Branch = action.Repository.Default_Branch - common.LogDebug(" + default branch", action.Repository.Default_Branch) + branch := config.Branch + if len(branch) == 0 { + if common.IsRemovedBranch(branch) { + common.LogDebug(" + default branch has removed suffix:", branch, "Skipping.") + return + } + branch = action.Repository.Default_Branch + common.LogDebug(" + using default branch", branch) } prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj) @@ -197,11 +207,11 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--depth", "1", "--checkout", action.Repository.Name) defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all") - if err := git.GitExec(filepath.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "--force", remoteName, config.Branch+":"+config.Branch); err != nil { - common.LogError("Error fetching branch:", config.Branch, "Ignoring as non-existent.", err) + if err := git.GitExec(filepath.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "--force", remoteName, branch+":"+branch); err != nil { + common.LogError("Error fetching branch:", branch, "Ignoring as non-existent.", err) return } - id, err := git.GitRemoteHead(filepath.Join(gitPrj, action.Repository.Name), remoteName, config.Branch) + id, err := git.GitRemoteHead(filepath.Join(gitPrj, action.Repository.Name), remoteName, branch) common.PanicOnError(err) if action.Head_Commit.Id == id { git.GitExecOrPanic(filepath.Join(gitPrj, action.Repository.Name), "checkout", id) @@ -212,7 +222,7 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common return } - common.LogDebug("push of refs not on the configured branch", config.Branch, ". ignoring.") + common.LogDebug("push of refs not on the configured branch", branch, ". ignoring.") } func verifyProjectState(git common.Git, org string, config *common.AutogitConfig, configs []*common.AutogitConfig) (err error) { @@ -268,14 +278,25 @@ next_package: } //} - common.LogDebug(" verifying package: %s -> %s(%s)", commitId, filename, config.Branch) - commits, err := gitea.GetRecentCommits(org, filename, config.Branch, 10) - if len(commits) == 0 { - if repo, err := gitea.GetRepository(org, filename); repo == nil && err == nil { + branch := config.Branch + common.LogDebug(" verifying package: %s -> %s(%s)", commitId, filename, branch) + if repo, err := gitea.GetRepository(org, filename); repo == nil && err == nil { + common.LogDebug(" repository removed...") + git.GitExecOrPanic(gitPrj, "rm", filename) + isGitUpdated = true + continue + } + if len(branch) == 0 { + branch = repo.DefaultBranch + if common.IsRemovedBranch(branch) { + common.LogDebug(" Default branch for", filename, "is excluded") git.GitExecOrPanic(gitPrj, "rm", filename) isGitUpdated = true + continue } } + + commits, err := gitea.GetRecentCommits(org, filename, branch, 10) if err != nil { common.LogDebug(" -> failed to fetch recent commits for package:", filename, " Err:", err) continue @@ -308,7 +329,7 @@ next_package: nCommits := len(common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgPath, "rev-list", "^NOW", "HEAD"), "\n")) if nCommits > 0 { if !noop { - git.GitExecOrPanic(pkgPath, "push", "-f", "origin", "HEAD:"+config.Branch) + git.GitExecOrPanic(pkgPath, "push", "-f", "origin", "HEAD:"+branch) } isGitUpdated = true } @@ -362,23 +383,30 @@ next_repo: common.LogDebug(" -- checking repository:", r.Name) - if _, err := gitea.GetRecentCommits(org, r.Name, config.Branch, 1); err != nil { + branch := config.Branch + if len(branch) == 0 { + branch = r.DefaultBranch + if common.IsRemovedBranch(branch) { + continue + } + } + if commits, err := gitea.GetRecentCommits(org, r.Name, branch, 1); err != nil || len(commits) == 0 { // assumption that package does not exist, so not part of project // https://github.com/go-gitea/gitea/issues/31976 + + // or, we do not have commits here continue } // add repository to git project common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--depth", "1", r.CloneURL, r.Name)) - if len(config.Branch) > 0 { - branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current")) - if branch != config.Branch { - if err := git.GitExec(path.Join(gitPrj, r.Name), "fetch", "--depth", "1", "origin", config.Branch+":"+config.Branch); err != nil { - return fmt.Errorf("Fetching branch %s for %s/%s failed. Ignoring.", config.Branch, repo.Owner.UserName, r.Name) - } - common.PanicOnError(git.GitExec(path.Join(gitPrj, r.Name), "checkout", config.Branch)) + curBranch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current")) + if branch != curBranch { + if err := git.GitExec(path.Join(gitPrj, r.Name), "fetch", "--depth", "1", "origin", branch+":"+branch); err != nil { + return fmt.Errorf("Fetching branch %s for %s/%s failed. Ignoring.", branch, repo.Owner.UserName, r.Name) } + common.PanicOnError(git.GitExec(path.Join(gitPrj, r.Name), "checkout", branch)) } isGitUpdated = true -- 2.51.1 From c25d3be44e6cebf6b929252d1805950e781b0b97b69524ab5982203a5d3f193f Mon Sep 17 00:00:00 2001 From: Adam Majer Date: Wed, 5 Nov 2025 13:24:54 +0100 Subject: [PATCH 5/5] direct: add systemd unit file --- autogits.spec | 2 ++ systemd/workflow-direct@.service | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 systemd/workflow-direct@.service diff --git a/autogits.spec b/autogits.spec index 56264f4..c450d7a 100644 --- a/autogits.spec +++ b/autogits.spec @@ -173,6 +173,7 @@ install -D -m0644 systemd/obs-staging-bot.service install -D -m0755 obs-status-service/obs-status-service %{buildroot}%{_bindir}/obs-status-service install -D -m0644 systemd/obs-status-service.service %{buildroot}%{_unitdir}/obs-status-service.service install -D -m0755 workflow-direct/workflow-direct %{buildroot}%{_bindir}/workflow-direct +install -D -m0644 systemd/workflow-direct@.service %{buildroot}%{_unitdir}/workflow-direct@.service install -D -m0755 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson @@ -261,6 +262,7 @@ install -D -m0755 utils/hujson/hujson %license COPYING %doc workflow-direct/README.md %{_bindir}/workflow-direct +%{_unitdir}/workflow-direct@.service %files workflow-pr %license COPYING diff --git a/systemd/workflow-direct@.service b/systemd/workflow-direct@.service new file mode 100644 index 0000000..17aaf0c --- /dev/null +++ b/systemd/workflow-direct@.service @@ -0,0 +1,15 @@ +[Unit] +Description=WorkflowDirect git bot for %i +After=network-online.target + +[Service] +Type=exec +ExecStart=/usr/bin/workflow-direct +EnvironmentFile=-/etc/default/%i/workflow-direct.env +DynamicUser=yes +NoNewPrivileges=yes +ProtectSystem=strict + +[Install] +WantedBy=multi-user.target + -- 2.51.1