2
1
forked from adamm/autogits

8 Commits

Author SHA256 Message Date
3d8671a7fe WIP: conflict resolution 2025-07-15 23:24:50 +02:00
c5db1c83a7 PR: detect and rebase project git commits
When project is advanced, and we have other package changes
to same project, the project git changes need to be rebased. The
simplest way of doing this is to skip all the submodule conflicts
and re-create them. This allows the submodules changes to be
mergeable again.
2025-07-15 19:08:05 +02:00
9f0909621b PR: fix timeline fetches
only fetch latest reviews from a user, not all
2025-07-15 11:06:17 +02:00
b3914b04bd Fix logic in crash protection
We must not access review.User object if it is nil
2025-07-11 12:02:37 +02:00
b43a19189e Enable code stream publishing 2025-07-11 12:02:08 +02:00
01b665230e message typo 2025-07-11 12:01:58 +02:00
1a07d4c541 Create Pull Requests to specified branches
instead of always using DefaultBranch. This means that target needs
always gets specified now.
2025-07-11 12:01:43 +02:00
22e44dff47 Don't fail on project git pull request creation. 2025-07-11 12:01:24 +02:00
11 changed files with 258 additions and 118 deletions

View File

@@ -769,6 +769,8 @@ func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleLi
done.Lock() done.Lock()
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)} data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)}
LogDebug("Getting submodules for:", commitId)
go func() { go func() {
defer done.Unlock() defer done.Unlock()
defer close(data_out.ch) defer close(data_out.ch)
@@ -802,7 +804,6 @@ func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleLi
for _, te := range tree.items { for _, te := range tree.items {
if te.isTree() { if te.isTree() {
trees[p+te.name+"/"] = te.hash trees[p+te.name+"/"] = te.hash
submoduleList[p+te.name] = te.hash
} else if te.isSubmodule() { } else if te.isSubmodule() {
submoduleList[p+te.name] = te.hash submoduleList[p+te.name] = te.hash
} }
@@ -917,6 +918,16 @@ type GitStatusData struct {
Path string Path string
Status int Status int
States [3]string States [3]string
/*
<sub> A 4 character field describing the submodule state.
"N..." when the entry is not a submodule.
"S<c><m><u>" when the entry is a submodule.
<c> is "C" if the commit changed; otherwise ".".
<m> is "M" if it has tracked changes; otherwise ".".
<u> is "U" if there are untracked changes; otherwise ".".
*/
SubmoduleChanges string
} }
func parseGitStatusHexString(data io.ByteReader) (string, error) { func parseGitStatusHexString(data io.ByteReader) (string, error) {
@@ -939,6 +950,20 @@ func parseGitStatusHexString(data io.ByteReader) (string, error) {
} }
} }
func parseGitStatusString(data io.ByteReader) (string, error) { func parseGitStatusString(data io.ByteReader) (string, error) {
str := make([]byte, 0, 100)
for {
c, err := data.ReadByte()
if err != nil {
return "", errors.New("Unexpected EOF. Expected NUL string term")
}
if c == 0 || c == ' ' {
return string(str), nil
}
str = append(str, c)
}
}
func parseGitStatusStringWithSpace(data io.ByteReader) (string, error) {
str := make([]byte, 0, 100) str := make([]byte, 0, 100)
for { for {
c, err := data.ReadByte() c, err := data.ReadByte()
@@ -979,7 +1004,7 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
return nil, err return nil, err
} }
ret.Status = GitStatus_Modified ret.Status = GitStatus_Modified
ret.Path, err = parseGitStatusString(data) ret.Path, err = parseGitStatusStringWithSpace(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -989,11 +1014,11 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
return nil, err return nil, err
} }
ret.Status = GitStatus_Renamed ret.Status = GitStatus_Renamed
ret.Path, err = parseGitStatusString(data) ret.Path, err = parseGitStatusStringWithSpace(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ret.States[0], err = parseGitStatusString(data) ret.States[0], err = parseGitStatusStringWithSpace(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1003,7 +1028,7 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
return nil, err return nil, err
} }
ret.Status = GitStatus_Untracked ret.Status = GitStatus_Untracked
ret.Path, err = parseGitStatusString(data) ret.Path, err = parseGitStatusStringWithSpace(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1013,15 +1038,22 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
return nil, err return nil, err
} }
ret.Status = GitStatus_Ignored ret.Status = GitStatus_Ignored
ret.Path, err = parseGitStatusString(data) ret.Path, err = parseGitStatusStringWithSpace(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
case 'u': case 'u':
var err error var err error
if err = skipGitStatusEntry(data, 7); err != nil { if err = skipGitStatusEntry(data, 2); err != nil {
return nil, err return nil, err
} }
if ret.SubmoduleChanges, err = parseGitStatusString(data); err != nil {
return nil, err
}
if err = skipGitStatusEntry(data, 4); err != nil {
return nil, err
}
if ret.States[0], err = parseGitStatusHexString(data); err != nil { if ret.States[0], err = parseGitStatusHexString(data); err != nil {
return nil, err return nil, err
} }
@@ -1032,7 +1064,7 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
return nil, err return nil, err
} }
ret.Status = GitStatus_Unmerged ret.Status = GitStatus_Unmerged
ret.Path, err = parseGitStatusString(data) ret.Path, err = parseGitStatusStringWithSpace(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -555,6 +555,8 @@ func TestGitStatusParse(t *testing.T) {
Path: ".gitmodules", Path: ".gitmodules",
Status: GitStatus_Unmerged, Status: GitStatus_Unmerged,
States: [3]string{"587ec403f01113f2629da538f6e14b84781f70ac59c41aeedd978ea8b1253a76", "d23eb05d9ca92883ab9f4d28f3ec90c05f667f3a5c8c8e291bd65e03bac9ae3c", "087b1d5f22dbf0aa4a879fff27fff03568b334c90daa5f2653f4a7961e24ea33"}, States: [3]string{"587ec403f01113f2629da538f6e14b84781f70ac59c41aeedd978ea8b1253a76", "d23eb05d9ca92883ab9f4d28f3ec90c05f667f3a5c8c8e291bd65e03bac9ae3c", "087b1d5f22dbf0aa4a879fff27fff03568b334c90daa5f2653f4a7961e24ea33"},
SubmoduleChanges: "N...",
}, },
}, },
}, },

View File

@@ -718,20 +718,18 @@ func (gitea *GiteaTransport) AddComment(pr *models.PullRequest, comment string)
} }
func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) { func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
limit := int64(20)
page := int64(1) page := int64(1)
resCount := limit resCount := 1
retData := []*models.TimelineComment{} retData := []*models.TimelineComment{}
for resCount == limit { for resCount > 0 {
res, err := gitea.client.Issue.IssueGetCommentsAndTimeline( res, err := gitea.client.Issue.IssueGetCommentsAndTimeline(
issue.NewIssueGetCommentsAndTimelineParams(). issue.NewIssueGetCommentsAndTimelineParams().
WithOwner(org). WithOwner(org).
WithRepo(repo). WithRepo(repo).
WithIndex(idx). WithIndex(idx).
WithPage(&page). WithPage(&page),
WithLimit(&limit),
gitea.transport.DefaultAuthentication, gitea.transport.DefaultAuthentication,
) )
@@ -739,11 +737,13 @@ func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models
return nil, err return nil, err
} }
resCount = int64(len(res.Payload)) resCount = len(res.Payload)
LogDebug("page:", page, "len:", resCount)
page++ page++
retData = append(retData, res.Payload...) retData = append(retData, res.Payload...)
} }
LogDebug("total results:", len(retData))
slices.SortFunc(retData, func(a, b *models.TimelineComment) int { slices.SortFunc(retData, func(a, b *models.TimelineComment) int {
return time.Time(b.Created).Compare(time.Time(a.Created)) return time.Time(b.Created).Compare(time.Time(a.Created))

View File

@@ -551,6 +551,7 @@ func (c *ObsClient) DeleteProject(project string) error {
query.Add("force", "1") query.Add("force", "1")
url.RawQuery = query.Encode() url.RawQuery = query.Encode()
res, err := c.ObsRequestRaw("DELETE", url.String(), nil) res, err := c.ObsRequestRaw("DELETE", url.String(), nil)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -121,27 +121,28 @@ func FetchPRSet(user string, gitea GiteaPRTimelineFetcher, org, repo string, num
}, nil }, nil
} }
func (rs *PRSet) Contains(pr *models.PullRequest) bool { func (rs *PRSet) Find(pr *models.PullRequest) (*PRInfo, bool) {
for _, p := range rs.PRs { for _, p := range rs.PRs {
if p.PR.Base.RepoID == pr.Base.RepoID && if p.PR.Base.RepoID == pr.Base.RepoID &&
p.PR.Head.Sha == pr.Head.Sha && p.PR.Head.Sha == pr.Head.Sha &&
p.PR.Base.Name == pr.Base.Name { p.PR.Base.Name == pr.Base.Name {
return true return p, true
} }
} }
return false return nil, false
} }
func (rs *PRSet) AddPR(pr *models.PullRequest) error { func (rs *PRSet) AddPR(pr *models.PullRequest) *PRInfo {
if rs.Contains(pr) { if pr, found := rs.Find(pr); found {
return nil return pr
} }
rs.PRs = append(rs.PRs, &PRInfo{ prinfo := &PRInfo{
PR: pr, PR: pr,
}) }
return nil rs.PRs = append(rs.PRs, prinfo)
return prinfo
} }
func (rs *PRSet) IsPrjGitPR(pr *models.PullRequest) bool { func (rs *PRSet) IsPrjGitPR(pr *models.PullRequest) bool {
@@ -172,6 +173,15 @@ func (rs *PRSet) GetPrjGitPR() (*PRInfo, error) {
return nil, PRSet_PrjGitMissing return nil, PRSet_PrjGitMissing
} }
func (rs *PRSet) NeedRecreatingPrjGit(currentBranchHash string) bool {
pr, err := rs.GetPrjGitPR()
if err != nil {
return true
}
return pr.PR.Base.Sha == currentBranchHash
}
func (rs *PRSet) IsConsistent() bool { func (rs *PRSet) IsConsistent() bool {
prjpr_info, err := rs.GetPrjGitPR() prjpr_info, err := rs.GetPrjGitPR()
if err != nil { if err != nil {
@@ -302,7 +312,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
} }
pr.Reviews = r pr.Reviews = r
if !pr.Reviews.IsManualMergeOK() { if !pr.Reviews.IsManualMergeOK() {
LogInfo("Not approved manual merge") LogInfo("Not approved manual merge. PR:", pr.PR.URL)
return false return false
} }
} }

View File

@@ -0,0 +1,48 @@
package common
import (
"errors"
"strings"
)
var UnknownParser error = errors.New("Cannot parse path")
type PRConflictResolver interface {
/*
stage_content -> { merge_base (stage1), head (stage2), merge_head (stage3) }
*/
Resolve(path string, stage_contents [3]string) error
}
var resolvers []PRConflictResolver = []PRConflictResolver{
&submodule_conflict_resolver{},
}
func ResolveMergeConflict(path string, file_contents [3]string) error {
for _, r := range resolvers {
if err := r.Resolve(path, file_contents); err != UnknownParser {
return err
}
}
return UnknownParser
}
type submodule_conflict_resolver struct{}
func (*submodule_conflict_resolver) Resolve(path string, stage [3]string) error {
if path != ".gitmodules" {
return UnknownParser
}
return UnknownParser
}
type changes_file_resolver struct{}
func (*changes_file_resolver) Resolve(path string, stage [3]string) error {
if !strings.HasSuffix(path, ".changes") {
return UnknownParser
}
return UnknownParser
}

View File

@@ -0,0 +1,10 @@
package common_test
import "testing"
func ResolveSubmoduleConflicts(t *testing.T) {
}
func ResolveChangesFileConflict(t *testing.T) {
}

View File

@@ -15,7 +15,23 @@ import (
"src.opensuse.org/autogits/common/gitea-generated/models" "src.opensuse.org/autogits/common/gitea-generated/models"
mock_common "src.opensuse.org/autogits/common/mock" mock_common "src.opensuse.org/autogits/common/mock"
) )
/*
func TestCockpit(t *testing.T) {
common.SetLoggingLevel(common.LogLevelDebug)
gitea := common.AllocateGiteaTransport("https://src.opensuse.org")
tl, err := gitea.GetTimeline("cockpit", "cockpit", 29)
if err != nil {
t.Fatal("Fail to timeline", err)
}
t.Log(tl)
r, err := common.FetchGiteaReviews(gitea, []string{}, "cockpit", "cockpit", 29)
if err != nil {
t.Fatal("Error:", err)
}
t.Error(r)
}
*/
func reviewsToTimeline(reviews []*models.PullReview) []*models.TimelineComment { func reviewsToTimeline(reviews []*models.PullReview) []*models.TimelineComment {
timeline := make([]*models.TimelineComment, len(reviews)) timeline := make([]*models.TimelineComment, len(reviews))
for idx, review := range reviews { for idx, review := range reviews {

View File

@@ -25,26 +25,39 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, reviewers []string, org, r
return nil, err return nil, err
} }
reviews := make([]*models.PullReview, 0, 10) reviews := make([]*models.PullReview, 0, len(reviewers))
var comments []*models.TimelineComment var comments []*models.TimelineComment
alreadyHaveUserReview := func(user string) bool {
for _, r := range reviews {
if r.User != nil && r.User.UserName == user {
return true
}
}
return false
}
for idx, item := range timeline { for idx, item := range timeline {
if item.Type == TimelineCommentType_Review { if item.Type == TimelineCommentType_Review {
for _, r := range rawReviews { for _, r := range rawReviews {
if r.ID == item.ReviewID { if r.ID == item.ReviewID {
reviews = append(reviews, r) if !alreadyHaveUserReview(r.User.UserName) {
reviews = append(reviews, r)
}
break break
} }
} }
} else if item.Type == TimelineCommentType_Comment { } else if item.Type == TimelineCommentType_Comment {
comments = append(comments, item) comments = append(comments, item)
} else if item.Type == TimelineCommentType_PushPull { } else if item.Type == TimelineCommentType_PushPull {
LogDebug("cut-off", item.Created)
timeline = timeline[0:idx] timeline = timeline[0:idx]
break break
} else { } else {
LogDebug("Unhandled timeline type:", item.Type) LogDebug("Unhandled timeline type:", item.Type)
} }
} }
LogDebug("num comments:", len(comments), "reviews:", len(reviews), len(timeline))
return &PRReviews{ return &PRReviews{
reviews: reviews, reviews: reviews,
@@ -72,6 +85,7 @@ func (r *PRReviews) IsManualMergeOK() bool {
if c.Updated != c.Created { if c.Updated != c.Created {
continue continue
} }
LogDebug("comment:", c.User.UserName, c.Body)
if slices.Contains(r.reviewers, c.User.UserName) { if slices.Contains(r.reviewers, c.User.UserName) {
if bodyCommandManualMergeOK(c.Body) { if bodyCommandManualMergeOK(c.Body) {
return true return true

View File

@@ -263,7 +263,7 @@ func ProcessRepoBuildStatus(results, ref []common.PackageBuildStatus) (status Bu
return BuildStatusSummarySuccess, SomeSuccess return BuildStatusSummarySuccess, SomeSuccess
} }
func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) { func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string) (*common.ProjectMeta, error) {
common.LogDebug("repo content fetching ...") common.LogDebug("repo content fetching ...")
err := FetchPrGit(git, pr) err := FetchPrGit(git, pr)
if err != nil { if err != nil {
@@ -289,15 +289,7 @@ func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullReque
} }
} }
common.LogDebug("Trying first staging master project: ", stagingMasterPrj) meta, err := ObsClient.GetProjectMeta(buildPrj)
meta, err := ObsClient.GetProjectMeta(stagingMasterPrj)
if err == nil {
// success, so we use that staging master project as our build project
buildPrj = stagingMasterPrj
} else {
common.LogInfo("error fetching project meta for ", stagingMasterPrj, ". Fall Back to ", buildPrj)
meta, err = ObsClient.GetProjectMeta(buildPrj)
}
if err != nil { if err != nil {
common.LogError("error fetching project meta for", buildPrj, ". Err:", err) common.LogError("error fetching project meta for", buildPrj, ". Err:", err)
return nil, err return nil, err
@@ -422,8 +414,7 @@ func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea comm
var state RequestModification = RequestModificationSourceChanged var state RequestModification = RequestModificationSourceChanged
if meta == nil { if meta == nil {
// new build // new build
common.LogDebug(" Staging master:", config.StagingProject) meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject)
meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
if err != nil { if err != nil {
return RequestModificationNoChange, err return RequestModificationNoChange, err
} }
@@ -437,8 +428,6 @@ func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea comm
} else { } else {
err = ObsClient.SetProjectMeta(meta) err = ObsClient.SetProjectMeta(meta)
if err != nil { if err != nil {
x, _ := xml.MarshalIndent(meta, "", " ")
common.LogDebug(" meta:", string(x))
common.LogError("cannot create meta project:", err) common.LogError("cannot create meta project:", err)
return RequestModificationNoChange, err return RequestModificationNoChange, err
} }
@@ -654,6 +643,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
common.LogError("No PR associated with review:", org, "/", repo, "#", id, "Error:", err) common.LogError("No PR associated with review:", org, "/", repo, "#", id, "Error:", err)
return true, err return true, err
} }
common.LogDebug("PR state:", pr.State) common.LogDebug("PR state:", pr.State)
if pr.State == "closed" { if pr.State == "closed" {
// dismiss the review // dismiss the review
@@ -670,68 +660,40 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
} }
} }
// Fetching data if review, err := FetchOurLatestActionableReview(gitea, org, repo, id); err == nil {
review, review_error := FetchOurLatestActionableReview(gitea, org, repo, id) common.LogInfo("processing review", review.HTMLURL, "state", review.State)
if pr.State != "closed" && review_error != nil {
// Nothing to do
return true, nil
}
err = FetchPrGit(git, pr) err = FetchPrGit(git, pr)
if err != nil { if err != nil {
common.LogError("Cannot fetch PR git:", pr.URL) common.LogError("Cannot fetch PR git:", pr.URL)
return false, err return false, err
}
// we want the possibly pending modification here, in case stagings are added, etc.
// jobs of review team to deal with issues
common.LogDebug("QA configuration fetching ...", common.StagingConfigFile)
data, err := git.GitCatFile(pr.Head.Sha, pr.Head.Sha, common.StagingConfigFile)
if err != nil {
common.LogError("Staging config", common.StagingConfigFile, "not found in PR to the project. Aborting.")
if !IsDryRun {
_, err = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find project config in PR: "+common.ProjectConfigFile)
} }
return true, err
}
stagingConfig, err := common.ParseStagingConfig(data) // we want the possibly pending modification here, in case stagings are added, etc.
if err != nil { // jobs of review team to deal with issues
common.LogError("Error parsing config file", common.StagingConfigFile, err) common.LogDebug("QA configuration fetching ...", common.StagingConfigFile)
} data, err := git.GitCatFile(pr.Head.Sha, pr.Head.Sha, common.StagingConfigFile)
if err != nil {
if stagingConfig.ObsProject == "" { common.LogError("Staging config", common.StagingConfigFile, "not found in PR to the project. Aborting.")
common.LogError("Cannot find reference project for PR#", pr.Index) if !IsDryRun {
if !IsDryRun && review_error == nil { _, err = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find project config in PR: "+common.ProjectConfigFile)
_, err := gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find reference project") }
return true, err return true, err
} }
return true, nil
}
common.LogDebug("ObsProject:", stagingConfig.ObsProject) stagingConfig, err := common.ParseStagingConfig(data)
stagingProject := GetObsProjectAssociatedWithPr(stagingConfig, ObsClient.HomeProject, pr) if err != nil {
common.LogError("Error parsing config file", common.StagingConfigFile, err)
// Cleanup projects
if pr.State == "closed" {
// review is done, cleanup
common.LogInfo(" -- closed request, cleanup staging projects")
for _, setup := range stagingConfig.QA {
if !IsDryRun {
ObsClient.DeleteProject(stagingProject + ":" + setup.Name)
}
} }
if stagingProject != "" {
if !IsDryRun {
ObsClient.DeleteProject(stagingProject)
}
}
return true, nil
}
// Process review aka setup projects if stagingConfig.ObsProject == "" {
if review_error == nil { common.LogError("Cannot find reference project for PR#", pr.Index)
common.LogInfo("processing review", review.HTMLURL, "state", review.State) if !IsDryRun {
_, err := gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find reference project")
return true, err
}
return true, nil
}
meta, err := ObsClient.GetProjectMeta(stagingConfig.ObsProject) meta, err := ObsClient.GetProjectMeta(stagingConfig.ObsProject)
if err != nil { if err != nil {
@@ -765,6 +727,16 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
} }
} }
if stagingConfig.StagingProject != "" {
// staging project must either be nothing or be *under* the target project.
// other setups are currently not supported
// NOTE: this is user input, so we need some limits here
l := len(stagingConfig.ObsProject)
if l >= len(stagingConfig.StagingProject) || stagingConfig.ObsProject != stagingConfig.StagingProject[0:l] {
common.LogError("StagingProject (", stagingConfig.StagingProject, ") is not child of target project", stagingConfig.ObsProject)
}
}
if meta.Name != stagingConfig.ObsProject { if meta.Name != stagingConfig.ObsProject {
common.LogError("staging.config . ObsProject:", stagingConfig.ObsProject, " is not target project name", meta.Name) common.LogError("staging.config . ObsProject:", stagingConfig.ObsProject, " is not target project name", meta.Name)
if !IsDryRun { if !IsDryRun {
@@ -785,22 +757,17 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
common.LogDebug(" # head submodules:", len(headSubmodules)) common.LogDebug(" # head submodules:", len(headSubmodules))
common.LogDebug(" # base submodules:", len(baseSubmodules)) common.LogDebug(" # base submodules:", len(baseSubmodules))
modifiedPackages := make([]string, 0, 16) modifiedOrNew := make([]string, 0, 16)
newPackages := make([]string, 0, 16)
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 len(baseOid) > 0 { modifiedOrNew = append(modifiedOrNew, pkg)
modifiedPackages = append(modifiedPackages, pkg)
} else {
newPackages = append(newPackages, pkg)
}
common.LogDebug(pkg, ":", baseOid, "->", headOid) common.LogDebug(pkg, ":", baseOid, "->", headOid)
} }
} }
} }
if len(modifiedPackages) == 0 && len(newPackages) == 0 { if len(modifiedOrNew) == 0 {
rebuild_all := false || stagingConfig.RebuildAll rebuild_all := false || stagingConfig.RebuildAll
reviews, err := gitea.GetPullRequestReviews(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index) reviews, err := gitea.GetPullRequestReviews(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index)
@@ -877,13 +844,13 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
gitea.AddComment(pr, msg) gitea.AddComment(pr, msg)
} }
baseResult, err := ObsClient.LastBuildResults(stagingConfig.ObsProject, modifiedPackages...) baseResult, err := ObsClient.LastBuildResults(stagingConfig.ObsProject, modifiedOrNew...)
if err != nil { if err != nil {
common.LogError("failed fetching ref project status for", stagingConfig.ObsProject, ":", err) common.LogError("failed fetching ref project status for", stagingConfig.ObsProject, ":", err)
} }
stagingResult, err := ObsClient.BuildStatus(stagingProject) stagingResult, err := ObsClient.BuildStatus(stagingProject)
if err != nil { if err != nil {
common.LogError("failed fetching stage project status for", stagingProject, ":", err) common.LogError("failed fetching ref project status for", stagingProject, ":", err)
} }
buildStatus := ProcessBuildStatus(stagingResult, baseResult) buildStatus := ProcessBuildStatus(stagingResult, baseResult)

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"path" "path"
"runtime/debug" "runtime/debug"
"slices"
"strings" "strings"
"github.com/opentracing/opentracing-go/log" "github.com/opentracing/opentracing-go/log"
@@ -80,6 +81,9 @@ func AllocatePRProcessor(req *common.PullRequestWebhookEvent, configs common.Aut
} }
common.LogDebug("git path:", git.GetPath()) common.LogDebug("git path:", git.GetPath())
// git.GitExecOrPanic("", "config", "set", "--global", "advice.submoduleMergeConflict", "false")
// git.GitExecOrPanic("", "config", "set", "--global", "advice.mergeConflict", "false")
return &PRProcessor{ return &PRProcessor{
config: config, config: config,
git: git, git: git,
@@ -108,7 +112,7 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) ([]string,
revert := false revert := false
if pr.PR.State != "open" { if pr.PR.State != "open" {
prjGitPR, err := prset.GetPrjGitPR() prjGitPR, err := prset.GetPrjGitPR()
if prjGitPR != nil { if prjGitPR != nil {
// remove PR from PrjGit // remove PR from PrjGit
var valid bool var valid bool
@@ -163,9 +167,9 @@ func (pr *PRProcessor) CreatePRjGitPR(prjGitPRbranch string, prset *common.PRSet
common.LogError("Failed to fetch PrjGit repository data.", PrjGitOrg, PrjGitRepo, err) common.LogError("Failed to fetch PrjGit repository data.", PrjGitOrg, PrjGitRepo, err)
return err return err
} }
remoteName, err := git.GitClone(common.DefaultGitPrj, PrjGitBranch, PrjGit.SSHURL) RemoteName, err := git.GitClone(common.DefaultGitPrj, PrjGitBranch, PrjGit.SSHURL)
common.PanicOnError(err) common.PanicOnError(err)
git.GitExecOrPanic(common.DefaultGitPrj, "checkout", "-B", prjGitPRbranch, remoteName+"/"+PrjGitBranch) git.GitExecOrPanic(common.DefaultGitPrj, "checkout", "-B", prjGitPRbranch, RemoteName+"/"+PrjGitBranch)
headCommit, err := git.GitBranchHead(common.DefaultGitPrj, prjGitPRbranch) headCommit, err := git.GitBranchHead(common.DefaultGitPrj, prjGitPRbranch)
if err != nil { if err != nil {
@@ -183,7 +187,7 @@ func (pr *PRProcessor) CreatePRjGitPR(prjGitPRbranch string, prset *common.PRSet
} }
if !common.IsDryRun && headCommit != newHeadCommit { if !common.IsDryRun && headCommit != newHeadCommit {
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "push", remoteName, "+HEAD:"+prjGitPRbranch)) common.PanicOnError(git.GitExec(common.DefaultGitPrj, "push", RemoteName, "+HEAD:"+prjGitPRbranch))
pr, err := Gitea.CreatePullRequestIfNotExist(PrjGit, prjGitPRbranch, PrjGitBranch, pr, err := Gitea.CreatePullRequestIfNotExist(PrjGit, prjGitPRbranch, PrjGitBranch,
"Forwarded PRs: "+strings.Join(title_refs, ", "), "Forwarded PRs: "+strings.Join(title_refs, ", "),
fmt.Sprintf("This is a forwarded pull request by %s\nreferencing the following pull request(s):\n\n", GitAuthor)+strings.Join(refs, ", "), fmt.Sprintf("This is a forwarded pull request by %s\nreferencing the following pull request(s):\n\n", GitAuthor)+strings.Join(refs, ", "),
@@ -196,12 +200,40 @@ func (pr *PRProcessor) CreatePRjGitPR(prjGitPRbranch string, prset *common.PRSet
RemoveDeadline: true, RemoveDeadline: true,
}) })
prset.AddPR(pr) prinfo := prset.AddPR(pr)
prinfo.RemoteName = RemoteName
} }
return nil return nil
} }
func (pr *PRProcessor) RebaseAndSkipSubmoduleCommits(prset *common.PRSet, branch string) error {
git := pr.git
PrjGitPR, err := prset.GetPrjGitPR()
common.PanicOnError(err)
remoteBranch := PrjGitPR.RemoteName + "/" + branch
common.LogDebug("Rebasing on top of", remoteBranch)
for conflict := git.GitExec(common.DefaultGitPrj, "rebase", remoteBranch); conflict != nil; {
statuses, err := git.GitStatus(common.DefaultGitPrj)
if err != nil {
git.GitExecOrPanic(common.DefaultGitPrj, "rebase", "--abort")
common.PanicOnError(err)
}
for _, s := range statuses {
if s.SubmoduleChanges != "S..." {
git.GitExecOrPanic(common.DefaultGitPrj, "rebase", "--abort")
return fmt.Errorf("Unexpected conflict in rebase. %s", s)
}
}
conflict = git.GitExec(common.DefaultGitPrj, "rebase", "--skip")
}
return nil
}
func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error { func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
_, _, PrjGitBranch := prset.Config.GetPrjGit()
PrjGitPR, err := prset.GetPrjGitPR() PrjGitPR, err := prset.GetPrjGitPR()
if err != nil { if err != nil {
common.LogError("Updating PrjGitPR but not found?", err) common.LogError("Updating PrjGitPR but not found?", err)
@@ -212,8 +244,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
remoteName, err := git.GitClone(common.DefaultGitPrj, prjGitPRbranch, PrjGit.SSHURL) PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, prjGitPRbranch, PrjGit.SSHURL)
common.PanicOnError(err) common.PanicOnError(err)
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", PrjGitPR.RemoteName, PrjGitBranch)
ExpectedMergeCommit, err := git.GitRemoteHead(common.DefaultGitPrj, PrjGitPR.RemoteName, PrjGitBranch)
forcePush := false
if ExpectedMergeCommit != PrjGitPR.PR.MergeBase {
common.PanicOnError(pr.RebaseAndSkipSubmoduleCommits(prset, PrjGitBranch))
forcePush = true
}
headCommit, err := git.GitBranchHead(common.DefaultGitPrj, prjGitPRbranch) headCommit, err := git.GitBranchHead(common.DefaultGitPrj, prjGitPRbranch)
if err != nil { if err != nil {
@@ -231,7 +271,11 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
} }
if !common.IsDryRun && headCommit != newHeadCommit { if !common.IsDryRun && headCommit != newHeadCommit {
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "push", remoteName, "+HEAD:"+prjGitPRbranch)) params := []string{"push", PrjGitPR.RemoteName, "+HEAD:" + prjGitPRbranch}
if forcePush {
params = slices.Insert(params, 1, "-f")
}
common.PanicOnError(git.GitExec(common.DefaultGitPrj, params...))
// update PR // update PR
PrjGitTitle := "Forwarded PRs: " + strings.Join(title_refs, ", ") PrjGitTitle := "Forwarded PRs: " + strings.Join(title_refs, ", ")
@@ -311,13 +355,10 @@ func (pr *PRProcessor) Process(req *common.PullRequestWebhookEvent) error {
// make sure that prjgit is consistent and only submodules that are to be *updated* // make sure that prjgit is consistent and only submodules that are to be *updated*
// reset anything that changed that is not part of the prset // reset anything that changed that is not part of the prset
// package removals/additions are *not* counted here // package removals/additions are *not* counted here
org, repo, branch := config.GetPrjGit()
if pr, err := prset.GetPrjGitPR(); err == nil { if pr, err := prset.GetPrjGitPR(); err == nil {
remote, err := git.GitClone(common.DefaultGitPrj, prjGitPRbranch, pr.PR.Base.Repo.CloneURL) common.LogDebug("Submodule parse begin")
common.PanicOnError(err) orig_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.RemoteName+"/"+branch) // merge base must remote branch, checked in prjgit udate
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", remote, pr.PR.MergeBase, pr.PR.Head.Ref)
common.LogDebug("Fetch done")
orig_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.PR.MergeBase)
common.PanicOnError(err) common.PanicOnError(err)
new_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.PR.Head.Sha) new_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.PR.Head.Sha)
common.PanicOnError(err) common.PanicOnError(err)
@@ -354,7 +395,6 @@ func (pr *PRProcessor) Process(req *common.PullRequestWebhookEvent) error {
} }
common.LogDebug(" num of reviewers:", len(prjGitPR.PR.RequestedReviewers)) common.LogDebug(" num of reviewers:", len(prjGitPR.PR.RequestedReviewers))
org, repo, branch := config.GetPrjGit()
maintainers, err := common.FetchProjectMaintainershipData(Gitea, org, repo, branch) maintainers, err := common.FetchProjectMaintainershipData(Gitea, org, repo, branch)
if err != nil { if err != nil {
return err return err