diff --git a/bots-common/git_utils.go b/bots-common/git_utils.go index a2679d5..75fb356 100644 --- a/bots-common/git_utils.go +++ b/bots-common/git_utils.go @@ -251,12 +251,26 @@ type tree_entry struct { name string mode int hash string + + size int } type tree struct { items []tree_entry } +func (t *tree_entry) isSubmodule() bool { + return (t.mode & 0170000) == 0160000 +} + +func (t *tree_entry) isTree() bool { + return (t.mode & 0170000) == 0040000 +} + +func (t *tree_entry) isBlob() bool { + return !t.isTree() && !t.isSubmodule() +} + func parseGitMsg(data <-chan byte) (gitMsg, error) { var id []byte = make([]byte, 64) var msgType []byte = make([]byte, 16) @@ -295,7 +309,7 @@ func parseGitMsg(data <-chan byte) (gitMsg, error) { if c >= '0' && c <= '9' { size = size*10 + (int(c) - '0') } else { - return gitMsg{}, errors.New("Invalid character during commit size parse") + return gitMsg{}, fmt.Errorf("Invalid character during object size parse: '%c'", c) } } @@ -366,31 +380,65 @@ func parseGitCommit(data <-chan byte) (commit, error) { return c, err } -func parseTreeEntry(data <-chan byte) (tree_entry, error) { +func parseTreeEntry(data <-chan byte, hashLen int) (tree_entry, error) { var e tree_entry - for c:=<-data; c != ' '; c=<-data { - e.mode = e.mode * 8 + (c - '0') + for c := <-data; c != ' '; c = <-data { + e.mode = e.mode*8 + int(c-'0') + e.size++ } - for c:=<-data; c != '\x00'; c=<-data { - e.name = append(e.name, c) + e.size++ + + name := make([]byte, 0, 128) + for c := <-data; c != '\x00'; c = <-data { + name = append(name, c) + e.size++ } + e.size++ + e.name = string(name) + + const hexBinToAscii = "0123456789abcdef" + + hash := make([]byte, 0, hashLen*2) + for range hashLen { + c := <-data + hash = append(hash, hexBinToAscii[((c&0xF0)>>4)], hexBinToAscii[c&0xF]) + } + e.hash = string(hash) + e.size += hashLen return e, nil } func parseGitTree(data <-chan byte) (tree, error) { - return tree{}, nil + hdr, err := parseGitMsg(data) + if err != nil { + return tree{}, err + } + + // max capacity to length of hash + t := tree{items: make([]tree_entry, 0, hdr.size/len(hdr.hash))} + for parsedLen := 0; parsedLen+1 < hdr.size; { + entry, err := parseTreeEntry(data, len(hdr.hash)/2) + if err != nil { + return tree{}, nil + } + + t.items = append(t.items, entry) + parsedLen += entry.size + } + + return t, nil } -func (e *RequestHandler) GitSubmoduleCommitId(cwd, packageName, headId string) (string, bool) { +func (e *RequestHandler) GitSubmoduleCommitId(cwd, packageName, commitId string) (string, bool) { if e.Error != nil { return "", false } data_in, data_out := ChanIO{make(chan byte, 256)}, ChanIO{make(chan byte, 70)} - var commitId string + var subCommitId string var foundLock sync.Mutex foundLock.Lock() @@ -399,7 +447,8 @@ func (e *RequestHandler) GitSubmoduleCommitId(cwd, packageName, headId string) ( defer foundLock.Unlock() defer close(data_out.ch) - data_out.Write([]byte("HEAD\n")) + data_out.Write([]byte(commitId)) + data_out.ch <- '\x00' c, err := parseGitCommit(data_in.ch) if err != nil { e.Error = err @@ -407,7 +456,7 @@ func (e *RequestHandler) GitSubmoduleCommitId(cwd, packageName, headId string) ( return } data_out.Write([]byte(c.Tree)) - data_out.ch <- '\n' + data_out.ch <- '\x00' tree, err := parseGitTree(data_in.ch) if err != nil { @@ -417,8 +466,8 @@ func (e *RequestHandler) GitSubmoduleCommitId(cwd, packageName, headId string) ( } for _, te := range tree.items { - if te.name == packageName { - commitId = te.hash + if te.name == packageName && te.isSubmodule() { + subCommitId = te.hash return } } @@ -431,5 +480,5 @@ func (e *RequestHandler) GitSubmoduleCommitId(cwd, packageName, headId string) ( e.Log("command run: %v", cmd.Run()) foundLock.Lock() - return commitId, len(commitId) == len(headId) + return subCommitId, len(subCommitId) == len(commitId) } diff --git a/bots-common/git_utils_test.go b/bots-common/git_utils_test.go index c78f3b9..47b3ce6 100644 --- a/bots-common/git_utils_test.go +++ b/bots-common/git_utils_test.go @@ -84,13 +84,13 @@ func TestGitMsgParsing(t *testing.T) { func TestGitCommitParsing(t *testing.T) { t.Run("parse valid commit message", func(t *testing.T) { - const commitData = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f99 commit 254\000"+ -`tree e20033df9f18780756ba4a96dbc7eb1a626253961039cb674156f266ba7a4e53 + const commitData = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f99 commit 254\000" + + `tree e20033df9f18780756ba4a96dbc7eb1a626253961039cb674156f266ba7a4e53 parent 429cc2fe02170ca5668f0461928c7e7430c7a17cd64ac298286d7162572a7703 author Adam Majer 1720709149 +0200 committer Adam Majer 1720709149 +0200 -.`+"\000" +.` + "\000" ch := make(chan byte, 5000) for _, b := range []byte(commitData) { ch <- b @@ -111,8 +111,8 @@ committer Adam Majer 1720709149 +0200 }) t.Run("parse multiline headers", func(t *testing.T) { - const commitData = "cae5831ab48470ff060a5aaa12eb6e5a7acaf91e commit 1492\x00" + -`tree 1f9c8fe8099615d6d3921528402ac53f09213b02 + const commitData = "cae5831ab48470ff060a5aaa12eb6e5a7acaf91e commit 1492\x00" + + `tree 1f9c8fe8099615d6d3921528402ac53f09213b02 parent e08a654fae0ecc91678819e0b62a2e014bad3339 author Yagiz Nizipli 1720967314 -0400 committer GitHub 1720967314 +0200 @@ -165,7 +165,7 @@ Reviewed-By: Marco Ippolito ` + "\x00" }) t.Run("parse tree object", func(t *testing.T) { - const treeData = "\x31\x31\x34\x31\x32\x39\x32\x30\x34\x38\x36\x38\x62\x30\x38\x65\x35\x36\x65\x39\x61\x66\x30\x64\x32\x39\x66\x37\x38\x36\x33\x34\x34\x37\x64\x32\x31\x36\x61\x62\x31\x35\x37\x32\x31\x30\x66\x65\x64\x37\x63\x36\x32\x37\x31\x64\x64\x35\x31\x30\x66\x35\x36\x62\x20\x74\x72\x65\x65\x20\x32\x30\x35\x0a\x34\x30\x30\x30\x30\x20\x62\x6f\x74\x73\x2d\x63\x6f\x6d\x6d\x6f\x6e\x00\x81\xac\x2e\xbd\x40\x77\xcb\x63\xbf\x40\x8c\xf8\xbc\xcd\x6f\xbe\x06\x0d\xfa\xd7\x45\x16\x85\x4f\xd0\xa0\x67\xb4\x21\x39\x11\x84\x34\x30\x30\x30\x30\x20\x6f\x62\x73\x2d\x73\x74\x61\x67\x69\x6e\x67\x2d\x62\x6f\x74\x00\x79\x77\x8b\x28\x7d\x37\x10\x59\xb9\x71\x28\x36\xed\x20\x31\x5f\xfb\xe1\xed\xb5\xba\x4f\x5e\xbb\x65\x65\x68\x23\x77\x32\x58\xfe\x34\x30\x30\x30\x30\x20\x70\x72\x2d\x72\x65\x76\x69\x65\x77\x00\x31\x6a\x4a\x1e\x67\xbb\xdd\x21\x89\xa4\x46\xf7\x62\x75\x60\x84\x73\x28\x6f\xb7\x3c\x51\x7f\x4a\x14\xa2\x28\xf9\x6e\x0b\xa7\x95\x34\x30\x30\x30\x30\x20\x70\x72\x6a\x67\x69\x74\x2d\x75\x70\x64\x61\x74\x65\x72\x00\xb4\x0b\x1c\xf5\xfb\xec\x9a\xb2\x9f\x48\x3e\x21\x18\x0d\x51\xb7\x98\x6e\x21\x99\x74\x84\x67\x71\x41\x24\x42\xfc\xc9\x04\x12\x99\x0a" + const treeData = "\x31\x61\x30\x35\x64\x62\x37\x33\x36\x39\x33\x37\x34\x33\x30\x65\x31\x38\x64\x66\x34\x33\x61\x32\x37\x61\x39\x38\x30\x30\x31\x30\x31\x32\x65\x31\x65\x64\x32\x30\x34\x38\x32\x39\x38\x36\x37\x31\x32\x38\x66\x32\x63\x65\x38\x34\x30\x36\x62\x35\x63\x66\x63\x39\x20\x74\x72\x65\x65\x20\x32\x30\x35\x00\x34\x30\x30\x30\x30\x20\x62\x6f\x74\x73\x2d\x63\x6f\x6d\x6d\x6f\x6e\x00\x93\x17\xaa\x47\xf6\xea\x37\xe8\xbc\xe2\x80\x77\x57\x90\xf4\xa8\x01\xd7\xe3\x70\x2f\x84\xfb\xe1\xb0\x0e\x4a\x2c\x1c\x75\x2c\x2b\x34\x30\x30\x30\x30\x20\x6f\x62\x73\x2d\x73\x74\x61\x67\x69\x6e\x67\x2d\x62\x6f\x74\x00\x79\x77\x8b\x28\x7d\x37\x10\x59\xb9\x71\x28\x36\xed\x20\x31\x5f\xfb\xe1\xed\xb5\xba\x4f\x5e\xbb\x65\x65\x68\x23\x77\x32\x58\xfe\x34\x30\x30\x30\x30\x20\x70\x72\x2d\x72\x65\x76\x69\x65\x77\x00\x36\x0d\x45\xcb\x76\xb8\x93\xb3\x21\xba\xfa\xd5\x00\x9d\xfc\x59\xab\x88\xc1\x3c\x81\xcb\x48\x5a\xe0\x29\x29\x0f\xe3\x6b\x3c\x5e\x34\x30\x30\x30\x30\x20\x70\x72\x6a\x67\x69\x74\x2d\x75\x70\x64\x61\x74\x65\x72\x00\xb4\x0b\x1c\xf5\xfb\xec\x9a\xb2\x9f\x48\x3e\x21\x18\x0d\x51\xb7\x98\x6e\x21\x99\x74\x84\x67\x71\x41\x24\x42\xfc\xc9\x04\x12\x99\x00" ch := make(chan byte, 1000) for _, b := range []byte(treeData) { @@ -179,9 +179,11 @@ Reviewed-By: Marco Ippolito ` + "\x00" } found := false + t.Log(tree.items) for _, item := range tree.items { - if item.name == "bots-common" && item.hash == "81ac2ebd4077cb63bf408cf8bccd6fbe060dfad74516854fd0a067b421391184" && item.mode == 0o40000 { + if item.name == "bots-common" && item.hash == "9317aa47f6ea37e8bce280775790f4a801d7e3702f84fbe1b00e4a2c1c752c2b" && item.isTree() { found = true + t.Log("found") break } } @@ -207,7 +209,7 @@ Reviewed-By: Marco Ippolito ` + "\x00" found := false for _, item := range tree.items { t.Log(item) - if item.name == "nodejs22" && item.hash == "873a323b262ebb3bd77b2592b2e11bdd08dbc721cbf4ac9f97637e58e1fffce7" { + if item.name == "nodejs22" && item.hash == "873a323b262ebb3bd77b2592b2e11bdd08dbc721cbf4ac9f97637e58e1fffce7" && item.isSubmodule() { found = true break } diff --git a/pr-review/main.go b/pr-review/main.go index 1e523bb..9ac06a7 100644 --- a/pr-review/main.go +++ b/pr-review/main.go @@ -5,6 +5,7 @@ import ( "path" "src.opensuse.org/autogits/common" + "src.opensuse.org/autogits/common/gitea-generated/models" ) const ( @@ -24,6 +25,22 @@ func processPrjGitPullRequestSync(h *common.RequestHandler) error { return nil } +func prGitBranchNameForPR(req *common.PullRequestAction) string { + return fmt.Sprintf("PR_%s#%d", req.Repository.Name, req.Pull_Request.Number) +} + +func updateOrCreatePRBranch(h *common.RequestHandler, prjGit *models.Repository, commitMsg, branchName string) { + req := h.Data.(*common.PullRequestAction) + + h.GitExec("", "clone", "--depth", "1", prjGit.SSHURL, common.DefaultGitPrj) + h.GitExec(common.DefaultGitPrj, "checkout", "-B", branchName, prjGit.DefaultBranch) + h.GitExec(common.DefaultGitPrj, "submodule", "update", "--init", "--checkout", "--depth", "1", req.Repository.Name) + h.GitExec(path.Join(common.DefaultGitPrj, req.Repository.Name), "fetch", "--depth", "1", "origin", req.Pull_Request.Head.Sha) + h.GitExec(path.Join(common.DefaultGitPrj, req.Repository.Name), "checkout", req.Pull_Request.Head.Sha) + h.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", commitMsg) + h.GitExec(common.DefaultGitPrj, "push", "-f", "origin", branchName) +} + func processPullRequestSync(h *common.RequestHandler) error { req := h.Data.(*common.PullRequestAction) @@ -36,9 +53,26 @@ func processPullRequestSync(h *common.RequestHandler) error { prjPr := h.GetAssociatedPrjGitPR(req) h.GitExec("", "clone", "--branch", prjPr.Head.Name, "--depth", "1", prjPr.Head.Repo.SSHURL, common.DefaultGitPrj) - commitId := h.GitSubmoduleCommitId(common.DefaultGitPrj, req.Repository.Name) + commitId, ok := h.GitSubmoduleCommitId(common.DefaultGitPrj, req.Repository.Name, prjPr.Head.Ref) - return nil + if !ok { + return fmt.Errorf("Cannot fetch submodule commit id in prjgit for '%s'", req.Repository.Name) + } + + // nothing changed, still in sync + if commitId == req.Pull_Request.Head.Ref { + return nil + } + + commitMsg := fmt.Sprintf(`Sync PR + +Update to %s`, req.Pull_Request.Head.Sha) + + // we need to update prjgit PR with the new head hash + branchName := prGitBranchNameForPR(req) + updateOrCreatePRBranch(h, prjPr.Base.Repo, commitMsg, branchName) + + return h.Error } func processPullRequestOpened(h *common.RequestHandler) error { @@ -50,7 +84,7 @@ func processPullRequestOpened(h *common.RequestHandler) error { } // create PrjGit branch for buidling the pull request - branchName := fmt.Sprintf("PR_%s#%d", req.Repository.Name, req.Pull_Request.Number) + branchName := prGitBranchNameForPR(req) commitMsg := fmt.Sprintf(`auto-created for %s This commit was autocreated by %s @@ -64,13 +98,7 @@ PullRequest: %s/%s#%d`, req.Repository.Owner.Username, return h.Error } - h.GitExec("", "clone", "--depth", "1", prjGit.SSHURL, common.DefaultGitPrj) - h.GitExec(common.DefaultGitPrj, "checkout", "-B", branchName, prjGit.DefaultBranch) - h.GitExec(common.DefaultGitPrj, "submodule", "update", "--init", "--checkout", "--depth", "1", req.Repository.Name) - h.GitExec(path.Join(common.DefaultGitPrj, req.Repository.Name), "fetch", "--depth", "1", "origin", req.Pull_Request.Head.Sha) - h.GitExec(path.Join(common.DefaultGitPrj, req.Repository.Name), "checkout", req.Pull_Request.Head.Sha) - h.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", commitMsg) - h.GitExec(common.DefaultGitPrj, "push", "-f", "origin", branchName) + updateOrCreatePRBranch(h, prjGit, commitMsg, branchName) PR := h.CreatePullRequest(prjGit, branchName, prjGit.DefaultBranch, fmt.Sprintf("Forwarded PR: %s", req.Repository.Name), @@ -88,7 +116,7 @@ referencing the following pull request: // request build review h.RequestReviews(PR, common.Bot_BuildReview) - return nil + return h.Error } func processPullRequest(h *common.RequestHandler) error {