Compare commits
1 Commits
ibs_state
...
fix/timeli
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
6feb04a14d |
@@ -22,8 +22,6 @@ Release: 0
|
||||
Summary: GitWorkflow utilities
|
||||
License: GPL-2.0-or-later
|
||||
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: systemd-rpm-macros
|
||||
BuildRequires: go
|
||||
|
||||
@@ -59,7 +59,6 @@ type QAConfig struct {
|
||||
Name string
|
||||
Origin string
|
||||
Label string // requires this gitea lable to be set or skipped
|
||||
Labels []string // requires any of the lables to be set
|
||||
BuildDisableRepos []string // which repos to build disable in the new project
|
||||
}
|
||||
|
||||
@@ -343,13 +342,6 @@ func ParseStagingConfig(data []byte) (*StagingConfig, error) {
|
||||
if err := json.Unmarshal(data, &staging); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// backward compability, transfer legacy Label to new Labels array
|
||||
for _, setup := range staging.QA {
|
||||
if len(setup.Labels) == 0 && len(setup.Label) > 0 {
|
||||
setup.Labels = []string{setup.Label}
|
||||
setup.Label = ""
|
||||
}
|
||||
}
|
||||
|
||||
return &staging, nil
|
||||
}
|
||||
|
||||
@@ -396,17 +396,12 @@ func (e *GitHandlerImpl) GitExecQuietOrPanic(cwd string, params ...string) {
|
||||
}
|
||||
|
||||
type ChanIO struct {
|
||||
ch chan byte
|
||||
done chan struct{}
|
||||
ch chan byte
|
||||
}
|
||||
|
||||
func (c *ChanIO) Write(p []byte) (int, error) {
|
||||
for _, b := range p {
|
||||
select {
|
||||
case c.ch <- b:
|
||||
case <-c.done:
|
||||
return 0, io.EOF
|
||||
}
|
||||
c.ch <- b
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
@@ -415,32 +410,21 @@ func (c *ChanIO) Write(p []byte) (int, error) {
|
||||
func (c *ChanIO) Read(data []byte) (idx int, err error) {
|
||||
var ok bool
|
||||
|
||||
select {
|
||||
case data[idx], ok = <-c.ch:
|
||||
data[idx], ok = <-c.ch
|
||||
if !ok {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
idx++
|
||||
|
||||
for len(c.ch) > 0 && idx < len(data) {
|
||||
data[idx], ok = <-c.ch
|
||||
if !ok {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
idx++
|
||||
case <-c.done:
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
|
||||
for len(c.ch) > 0 && idx < len(data) {
|
||||
select {
|
||||
case data[idx], ok = <-c.ch:
|
||||
if !ok {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
idx++
|
||||
case <-c.done:
|
||||
err = io.EOF
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
idx++
|
||||
}
|
||||
|
||||
return
|
||||
@@ -487,14 +471,7 @@ func parseGitMsg(data <-chan byte) (GitMsg, error) {
|
||||
var size int
|
||||
|
||||
pos := 0
|
||||
for {
|
||||
c, ok := <-data
|
||||
if !ok {
|
||||
return GitMsg{}, io.EOF
|
||||
}
|
||||
if c == ' ' {
|
||||
break
|
||||
}
|
||||
for c := <-data; c != ' '; c = <-data {
|
||||
if (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') {
|
||||
id[pos] = c
|
||||
pos++
|
||||
@@ -506,15 +483,7 @@ func parseGitMsg(data <-chan byte) (GitMsg, error) {
|
||||
|
||||
pos = 0
|
||||
var c byte
|
||||
for {
|
||||
var ok bool
|
||||
c, ok = <-data
|
||||
if !ok {
|
||||
return GitMsg{}, io.EOF
|
||||
}
|
||||
if c == ' ' || c == '\x00' {
|
||||
break
|
||||
}
|
||||
for c = <-data; c != ' ' && c != '\x00'; c = <-data {
|
||||
if c >= 'a' && c <= 'z' {
|
||||
msgType[pos] = c
|
||||
pos++
|
||||
@@ -540,14 +509,7 @@ func parseGitMsg(data <-chan byte) (GitMsg, error) {
|
||||
return GitMsg{}, fmt.Errorf("Invalid object type: '%s'", string(msgType))
|
||||
}
|
||||
|
||||
for {
|
||||
c, ok := <-data
|
||||
if !ok {
|
||||
return GitMsg{}, io.EOF
|
||||
}
|
||||
if c == '\x00' {
|
||||
break
|
||||
}
|
||||
for c = <-data; c != '\000'; c = <-data {
|
||||
if c >= '0' && c <= '9' {
|
||||
size = size*10 + (int(c) - '0')
|
||||
} else {
|
||||
@@ -566,37 +528,18 @@ func parseGitCommitHdr(oldHdr [2]string, data <-chan byte) ([2]string, int, erro
|
||||
hdr := make([]byte, 0, 60)
|
||||
val := make([]byte, 0, 1000)
|
||||
|
||||
c, ok := <-data
|
||||
if !ok {
|
||||
return [2]string{}, 0, io.EOF
|
||||
}
|
||||
c := <-data
|
||||
size := 1
|
||||
if c != '\n' { // end of header marker
|
||||
for {
|
||||
if c == ' ' {
|
||||
break
|
||||
}
|
||||
for ; c != ' '; c = <-data {
|
||||
hdr = append(hdr, c)
|
||||
size++
|
||||
var ok bool
|
||||
c, ok = <-data
|
||||
if !ok {
|
||||
return [2]string{}, size, io.EOF
|
||||
}
|
||||
}
|
||||
if size == 1 { // continuation header here
|
||||
hdr = []byte(oldHdr[0])
|
||||
val = append([]byte(oldHdr[1]), '\n')
|
||||
}
|
||||
for {
|
||||
var ok bool
|
||||
c, ok = <-data
|
||||
if !ok {
|
||||
return [2]string{}, size, io.EOF
|
||||
}
|
||||
if c == '\n' {
|
||||
break
|
||||
}
|
||||
for c := <-data; c != '\n'; c = <-data {
|
||||
val = append(val, c)
|
||||
size++
|
||||
}
|
||||
@@ -609,14 +552,7 @@ func parseGitCommitHdr(oldHdr [2]string, data <-chan byte) ([2]string, int, erro
|
||||
func parseGitCommitMsg(data <-chan byte, l int) (string, error) {
|
||||
msg := make([]byte, 0, l)
|
||||
|
||||
for {
|
||||
c, ok := <-data
|
||||
if !ok {
|
||||
return string(msg), io.EOF
|
||||
}
|
||||
if c == '\x00' {
|
||||
break
|
||||
}
|
||||
for c := <-data; c != '\x00'; c = <-data {
|
||||
msg = append(msg, c)
|
||||
l--
|
||||
}
|
||||
@@ -642,7 +578,7 @@ func parseGitCommit(data <-chan byte) (GitCommit, error) {
|
||||
var hdr [2]string
|
||||
hdr, size, err := parseGitCommitHdr(hdr, data)
|
||||
if err != nil {
|
||||
return GitCommit{}, err
|
||||
return GitCommit{}, nil
|
||||
}
|
||||
l -= size
|
||||
|
||||
@@ -663,28 +599,14 @@ func parseGitCommit(data <-chan byte) (GitCommit, error) {
|
||||
func parseTreeEntry(data <-chan byte, hashLen int) (GitTreeEntry, error) {
|
||||
var e GitTreeEntry
|
||||
|
||||
for {
|
||||
c, ok := <-data
|
||||
if !ok {
|
||||
return e, io.EOF
|
||||
}
|
||||
if c == ' ' {
|
||||
break
|
||||
}
|
||||
for c := <-data; c != ' '; c = <-data {
|
||||
e.mode = e.mode*8 + int(c-'0')
|
||||
e.size++
|
||||
}
|
||||
e.size++
|
||||
|
||||
name := make([]byte, 0, 128)
|
||||
for {
|
||||
c, ok := <-data
|
||||
if !ok {
|
||||
return e, io.EOF
|
||||
}
|
||||
if c == '\x00' {
|
||||
break
|
||||
}
|
||||
for c := <-data; c != '\x00'; c = <-data {
|
||||
name = append(name, c)
|
||||
e.size++
|
||||
}
|
||||
@@ -695,10 +617,7 @@ func parseTreeEntry(data <-chan byte, hashLen int) (GitTreeEntry, error) {
|
||||
|
||||
hash := make([]byte, 0, hashLen*2)
|
||||
for range hashLen {
|
||||
c, ok := <-data
|
||||
if !ok {
|
||||
return e, io.EOF
|
||||
}
|
||||
c := <-data
|
||||
hash = append(hash, hexBinToAscii[((c&0xF0)>>4)], hexBinToAscii[c&0xF])
|
||||
}
|
||||
e.hash = string(hash)
|
||||
@@ -719,16 +638,13 @@ func parseGitTree(data <-chan byte) (GitTree, error) {
|
||||
for parsedLen < hdr.size {
|
||||
entry, err := parseTreeEntry(data, len(hdr.hash)/2)
|
||||
if err != nil {
|
||||
return GitTree{}, err
|
||||
return GitTree{}, nil
|
||||
}
|
||||
|
||||
t.items = append(t.items, entry)
|
||||
parsedLen += entry.size
|
||||
}
|
||||
c, ok := <-data // \0 read
|
||||
if !ok {
|
||||
return t, io.EOF
|
||||
}
|
||||
c := <-data // \0 read
|
||||
|
||||
if c != '\x00' {
|
||||
return t, fmt.Errorf("Unexpected character during git tree data read")
|
||||
@@ -749,16 +665,9 @@ func parseGitBlob(data <-chan byte) ([]byte, error) {
|
||||
|
||||
d := make([]byte, hdr.size)
|
||||
for l := 0; l < hdr.size; l++ {
|
||||
var ok bool
|
||||
d[l], ok = <-data
|
||||
if !ok {
|
||||
return d, io.EOF
|
||||
}
|
||||
}
|
||||
eob, ok := <-data
|
||||
if !ok {
|
||||
return d, io.EOF
|
||||
d[l] = <-data
|
||||
}
|
||||
eob := <-data
|
||||
if eob != '\x00' {
|
||||
return d, fmt.Errorf("invalid byte read in parseGitBlob")
|
||||
}
|
||||
@@ -770,25 +679,16 @@ func (e *GitHandlerImpl) GitParseCommits(cwd string, commitIDs []string) (parsed
|
||||
var done sync.Mutex
|
||||
|
||||
done.Lock()
|
||||
done_signal := make(chan struct{})
|
||||
var once sync.Once
|
||||
close_done := func() {
|
||||
once.Do(func() {
|
||||
close(done_signal)
|
||||
})
|
||||
}
|
||||
|
||||
data_in, data_out := ChanIO{make(chan byte), done_signal}, ChanIO{make(chan byte), done_signal}
|
||||
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)}
|
||||
parsedCommits = make([]GitCommit, 0, len(commitIDs))
|
||||
|
||||
go func() {
|
||||
defer done.Unlock()
|
||||
defer close_done()
|
||||
defer close(data_out.ch)
|
||||
|
||||
for _, id := range commitIDs {
|
||||
data_out.Write([]byte(id))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
c, e := parseGitCommit(data_in.ch)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Error parsing git commit: %w", e)
|
||||
@@ -815,14 +715,12 @@ func (e *GitHandlerImpl) GitParseCommits(cwd string, commitIDs []string) (parsed
|
||||
LogDebug("command run:", cmd.Args)
|
||||
if e := cmd.Run(); e != nil {
|
||||
LogError(e)
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
close(data_out.ch)
|
||||
return nil, e
|
||||
}
|
||||
|
||||
done.Lock()
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -831,21 +729,15 @@ func (e *GitHandlerImpl) GitCatFile(cwd, commitId, filename string) (data []byte
|
||||
var done sync.Mutex
|
||||
|
||||
done.Lock()
|
||||
done_signal := make(chan struct{})
|
||||
var once sync.Once
|
||||
close_done := func() {
|
||||
once.Do(func() {
|
||||
close(done_signal)
|
||||
})
|
||||
}
|
||||
data_in, data_out := ChanIO{make(chan byte), done_signal}, ChanIO{make(chan byte), done_signal}
|
||||
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)}
|
||||
|
||||
go func() {
|
||||
defer done.Unlock()
|
||||
defer close_done()
|
||||
defer close(data_out.ch)
|
||||
|
||||
data_out.Write([]byte(commitId))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
|
||||
var c GitCommit
|
||||
c, err = parseGitCommit(data_in.ch)
|
||||
if err != nil {
|
||||
@@ -853,9 +745,11 @@ func (e *GitHandlerImpl) GitCatFile(cwd, commitId, filename string) (data []byte
|
||||
return
|
||||
}
|
||||
data_out.Write([]byte(c.Tree))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
|
||||
var tree GitTree
|
||||
tree, err = parseGitTree(data_in.ch)
|
||||
|
||||
if err != nil {
|
||||
LogError("Error parsing git tree:", err)
|
||||
return
|
||||
@@ -865,7 +759,7 @@ func (e *GitHandlerImpl) GitCatFile(cwd, commitId, filename string) (data []byte
|
||||
if te.isBlob() && te.name == filename {
|
||||
LogInfo("blob", te.hash)
|
||||
data_out.Write([]byte(te.hash))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
data, err = parseGitBlob(data_in.ch)
|
||||
return
|
||||
}
|
||||
@@ -890,13 +784,11 @@ func (e *GitHandlerImpl) GitCatFile(cwd, commitId, filename string) (data []byte
|
||||
LogDebug("command run:", cmd.Args)
|
||||
if e := cmd.Run(); e != nil {
|
||||
LogError(e)
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
close(data_out.ch)
|
||||
return nil, e
|
||||
}
|
||||
done.Lock()
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -906,24 +798,16 @@ func (e *GitHandlerImpl) GitDirectoryList(gitPath, commitId string) (directoryLi
|
||||
directoryList = make(map[string]string)
|
||||
|
||||
done.Lock()
|
||||
done_signal := make(chan struct{})
|
||||
var once sync.Once
|
||||
close_done := func() {
|
||||
once.Do(func() {
|
||||
close(done_signal)
|
||||
})
|
||||
}
|
||||
data_in, data_out := ChanIO{make(chan byte), done_signal}, ChanIO{make(chan byte), done_signal}
|
||||
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)}
|
||||
|
||||
LogDebug("Getting directory for:", commitId)
|
||||
|
||||
go func() {
|
||||
defer done.Unlock()
|
||||
defer close_done()
|
||||
defer close(data_out.ch)
|
||||
|
||||
data_out.Write([]byte(commitId))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
var c GitCommit
|
||||
c, err = parseGitCommit(data_in.ch)
|
||||
if err != nil {
|
||||
@@ -939,7 +823,7 @@ func (e *GitHandlerImpl) GitDirectoryList(gitPath, commitId string) (directoryLi
|
||||
delete(trees, p)
|
||||
|
||||
data_out.Write([]byte(tree))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
var tree GitTree
|
||||
tree, err = parseGitTree(data_in.ch)
|
||||
|
||||
@@ -973,14 +857,12 @@ func (e *GitHandlerImpl) GitDirectoryList(gitPath, commitId string) (directoryLi
|
||||
LogDebug("command run:", cmd.Args)
|
||||
if e := cmd.Run(); e != nil {
|
||||
LogError(e)
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
close(data_out.ch)
|
||||
return directoryList, e
|
||||
}
|
||||
|
||||
done.Lock()
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
return directoryList, err
|
||||
}
|
||||
|
||||
@@ -990,14 +872,7 @@ func (e *GitHandlerImpl) GitDirectoryContentList(gitPath, commitId string) (dire
|
||||
directoryList = make(map[string]string)
|
||||
|
||||
done.Lock()
|
||||
done_signal := make(chan struct{})
|
||||
var once sync.Once
|
||||
close_done := func() {
|
||||
once.Do(func() {
|
||||
close(done_signal)
|
||||
})
|
||||
}
|
||||
data_in, data_out := ChanIO{make(chan byte), done_signal}, ChanIO{make(chan byte), done_signal}
|
||||
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)}
|
||||
|
||||
LogDebug("Getting directory content for:", commitId)
|
||||
|
||||
@@ -1006,7 +881,7 @@ func (e *GitHandlerImpl) GitDirectoryContentList(gitPath, commitId string) (dire
|
||||
defer close(data_out.ch)
|
||||
|
||||
data_out.Write([]byte(commitId))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
var c GitCommit
|
||||
c, err = parseGitCommit(data_in.ch)
|
||||
if err != nil {
|
||||
@@ -1022,7 +897,7 @@ func (e *GitHandlerImpl) GitDirectoryContentList(gitPath, commitId string) (dire
|
||||
delete(trees, p)
|
||||
|
||||
data_out.Write([]byte(tree))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
var tree GitTree
|
||||
tree, err = parseGitTree(data_in.ch)
|
||||
|
||||
@@ -1058,14 +933,12 @@ func (e *GitHandlerImpl) GitDirectoryContentList(gitPath, commitId string) (dire
|
||||
LogDebug("command run:", cmd.Args)
|
||||
if e := cmd.Run(); e != nil {
|
||||
LogError(e)
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
close(data_out.ch)
|
||||
return directoryList, e
|
||||
}
|
||||
|
||||
done.Lock()
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
return directoryList, err
|
||||
}
|
||||
|
||||
@@ -1075,24 +948,16 @@ func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleLi
|
||||
submoduleList = make(map[string]string)
|
||||
|
||||
done.Lock()
|
||||
done_signal := make(chan struct{})
|
||||
var once sync.Once
|
||||
close_done := func() {
|
||||
once.Do(func() {
|
||||
close(done_signal)
|
||||
})
|
||||
}
|
||||
data_in, data_out := ChanIO{make(chan byte), done_signal}, ChanIO{make(chan byte), done_signal}
|
||||
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)}
|
||||
|
||||
LogDebug("Getting submodules for:", commitId)
|
||||
|
||||
go func() {
|
||||
defer done.Unlock()
|
||||
defer close_done()
|
||||
defer close(data_out.ch)
|
||||
|
||||
data_out.Write([]byte(commitId))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
var c GitCommit
|
||||
c, err = parseGitCommit(data_in.ch)
|
||||
if err != nil {
|
||||
@@ -1108,7 +973,7 @@ func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleLi
|
||||
delete(trees, p)
|
||||
|
||||
data_out.Write([]byte(tree))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
var tree GitTree
|
||||
tree, err = parseGitTree(data_in.ch)
|
||||
|
||||
@@ -1145,26 +1010,17 @@ func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleLi
|
||||
LogDebug("command run:", cmd.Args)
|
||||
if e := cmd.Run(); e != nil {
|
||||
LogError(e)
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
close(data_out.ch)
|
||||
return submoduleList, e
|
||||
}
|
||||
|
||||
done.Lock()
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
return submoduleList, err
|
||||
}
|
||||
|
||||
func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string) (subCommitId string, valid bool) {
|
||||
done_signal := make(chan struct{})
|
||||
var once sync.Once
|
||||
close_done := func() {
|
||||
once.Do(func() {
|
||||
close(done_signal)
|
||||
})
|
||||
}
|
||||
data_in, data_out := ChanIO{make(chan byte), done_signal}, ChanIO{make(chan byte), done_signal}
|
||||
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)}
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
@@ -1180,18 +1036,17 @@ func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string)
|
||||
}()
|
||||
|
||||
defer wg.Done()
|
||||
defer close_done()
|
||||
defer close(data_out.ch)
|
||||
|
||||
data_out.Write([]byte(commitId))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
c, err := parseGitCommit(data_in.ch)
|
||||
if err != nil {
|
||||
LogError("Error parsing git commit:", err)
|
||||
panic(err)
|
||||
}
|
||||
data_out.Write([]byte(c.Tree))
|
||||
data_out.Write([]byte{0})
|
||||
data_out.ch <- '\x00'
|
||||
tree, err := parseGitTree(data_in.ch)
|
||||
|
||||
if err != nil {
|
||||
@@ -1223,14 +1078,12 @@ func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string)
|
||||
LogDebug("command run:", cmd.Args)
|
||||
if e := cmd.Run(); e != nil {
|
||||
LogError(e)
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
close(data_out.ch)
|
||||
return subCommitId, false
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close_done()
|
||||
close(data_in.ch)
|
||||
return subCommitId, len(subCommitId) > 0
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGitClone(t *testing.T) {
|
||||
@@ -718,44 +717,3 @@ func TestGitDirectoryListRepro(t *testing.T) {
|
||||
t.Errorf("Expected 'subdir' in directory list, got %v", dirs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitDeadlockFix(t *testing.T) {
|
||||
gitDir := t.TempDir()
|
||||
testDir, _ := os.Getwd()
|
||||
|
||||
cmd := exec.Command("/usr/bin/bash", path.Join(testDir, "tsetup.sh"))
|
||||
cmd.Dir = gitDir
|
||||
_, err := cmd.CombinedOutput()
|
||||
|
||||
gh, err := AllocateGitWorkTree(gitDir, "Test", "test@example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h, err := gh.ReadExistingPath(".")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer h.Close()
|
||||
|
||||
// Use a blob ID to trigger error in GitParseCommits
|
||||
// This ensures that the function returns error immediately and doesn't deadlock
|
||||
blobId := "81aba862107f1e2f5312e165453955485f424612f313d6c2fb1b31fef9f82a14"
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
_, err := h.GitParseCommits("", []string{blobId})
|
||||
done <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
if err == nil {
|
||||
t.Error("Expected error from GitParseCommits with blob ID, got nil")
|
||||
} else {
|
||||
// This is expected
|
||||
t.Logf("Got expected error: %v", err)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("GitParseCommits deadlocked! Fix is NOT working.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -863,9 +863,10 @@ func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models
|
||||
TimelineCache, IsCached := giteaTimelineCache[prID]
|
||||
var LastCachedTime strfmt.DateTime
|
||||
if IsCached {
|
||||
l := len(TimelineCache.data)
|
||||
if l > 0 {
|
||||
LastCachedTime = TimelineCache.data[0].Updated
|
||||
for _, d := range TimelineCache.data {
|
||||
if time.Time(d.Updated).Compare(time.Time(LastCachedTime)) > 0 {
|
||||
LastCachedTime = d.Updated
|
||||
}
|
||||
}
|
||||
|
||||
// cache data for 5 seconds
|
||||
@@ -894,14 +895,20 @@ func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models
|
||||
}
|
||||
|
||||
for _, d := range res.Payload {
|
||||
if d != nil {
|
||||
if time.Time(d.Created).Compare(time.Time(LastCachedTime)) > 0 {
|
||||
// created after last check, so we append here
|
||||
TimelineCache.data = append(TimelineCache.data, d)
|
||||
} else {
|
||||
// we need something updated in the timeline, maybe
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
found := false
|
||||
for i := range TimelineCache.data {
|
||||
if TimelineCache.data[i].ID == d.ID {
|
||||
TimelineCache.data[i] = d
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
TimelineCache.data = append(TimelineCache.data, d)
|
||||
}
|
||||
}
|
||||
|
||||
if resCount < 10 {
|
||||
|
||||
@@ -137,108 +137,6 @@ index 0000000..e69de29
|
||||
assert project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was not merged after 'merge ok'."
|
||||
print("Both PRs merged successfully after 'merge ok'.")
|
||||
|
||||
@pytest.mark.t003
|
||||
def test_003_refuse_manual_merge(manual_merge_env, test_user_client, ownerB_client, staging_bot_client):
|
||||
"""
|
||||
Test scenario TC-MERGE-003:
|
||||
1. Create a PackageGit PR with ManualMergeOnly set to true.
|
||||
2. Ensure all mandatory reviews are completed on both project and package PRs.
|
||||
3. Comment "merge ok" on the package PR from the account of a not requested reviewer.
|
||||
4. Verify the PR is not merged.
|
||||
"""
|
||||
gitea_env, test_full_repo_name, merge_branch_name = manual_merge_env
|
||||
|
||||
# 1. Create a package PR
|
||||
diff = """diff --git a/manual_merge_test.txt b/manual_merge_test.txt
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
"""
|
||||
print(f"--- Creating package PR in mypool/pkgA on branch {merge_branch_name} ---")
|
||||
package_pr = test_user_client.create_gitea_pr("mypool/pkgA", diff, "Test Manual Merge Fixture", False, base_branch=merge_branch_name)
|
||||
package_pr_number = package_pr["number"]
|
||||
print(f"Created package PR mypool/pkgA#{package_pr_number}")
|
||||
|
||||
# 2. Make sure the workflow-pr service created related project PR
|
||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", package_pr_number)
|
||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
||||
|
||||
# 3. Approve reviews and verify NOT merged
|
||||
print("Waiting for all expected review requests and approving them...")
|
||||
# Expected reviewers based on manual-merge branch config and pkgA maintainership
|
||||
expected_reviewers = {"usera", "userb", "ownerA", "ownerX", "ownerY"}
|
||||
|
||||
# ManualMergeOnly still requires regular reviews to be satisfied.
|
||||
# We poll until all expected reviewers are requested, then approve them.
|
||||
all_requested = False
|
||||
for _ in range(30):
|
||||
# Trigger approvals for whatever is already requested
|
||||
gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number)
|
||||
gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number)
|
||||
|
||||
# Explicitly handle staging bot if it is requested or pending
|
||||
prj_reviews = gitea_env.list_reviews("myproducts/mySLFO", project_pr_number)
|
||||
if any(r["user"]["login"] == "autogits_obs_staging_bot" and r["state"] in ["REQUEST_REVIEW", "PENDING"] for r in prj_reviews):
|
||||
print("Staging bot has a pending/requested review. Approving...")
|
||||
staging_bot_client.create_review("myproducts/mySLFO", project_pr_number, event="APPROVED", body="Staging bot approves")
|
||||
|
||||
# Check if all expected reviewers have at least one review record (any state)
|
||||
pkg_reviews = gitea_env.list_reviews("mypool/pkgA", package_pr_number)
|
||||
current_reviewers = {r["user"]["login"] for r in pkg_reviews}
|
||||
|
||||
if expected_reviewers.issubset(current_reviewers):
|
||||
# Also ensure they are all approved (not just requested)
|
||||
approved_reviewers = {r["user"]["login"] for r in pkg_reviews if r["state"] == "APPROVED"}
|
||||
if expected_reviewers.issubset(approved_reviewers):
|
||||
# And check project PR for bot approval
|
||||
prj_approved = any(r["user"]["login"] == "autogits_obs_staging_bot" and r["state"] == "APPROVED" for r in prj_reviews)
|
||||
if prj_approved:
|
||||
all_requested = True
|
||||
print(f"All expected reviewers {expected_reviewers} and staging bot have approved.")
|
||||
break
|
||||
|
||||
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
|
||||
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
|
||||
|
||||
assert not pkg_details.get("merged"), "Package PR merged prematurely (ManualMergeOnly ignored?)"
|
||||
assert not prj_details.get("merged"), "Project PR merged prematurely (ManualMergeOnly ignored?)"
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
assert all_requested, f"Timed out waiting for all expected reviewers {expected_reviewers} to approve. Current: {current_reviewers}"
|
||||
print("Both PRs have all required approvals but are not merged (as expected with ManualMergeOnly).")
|
||||
|
||||
# 4. Comment "merge ok" from a requested reviewer (ownerB)
|
||||
print("Commenting 'merge ok' on package PR as user ownerB ...")
|
||||
ownerB_client.create_issue_comment("mypool/pkgA", package_pr_number, "merge ok")
|
||||
|
||||
# 5. Verify both PRs are merged
|
||||
print("Polling for PR merge status...")
|
||||
package_merged = False
|
||||
project_merged = False
|
||||
|
||||
for i in range(20): # Poll for up to 20 seconds
|
||||
if not package_merged:
|
||||
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
|
||||
if pkg_details.get("merged"):
|
||||
package_merged = True
|
||||
print(f"Package PR mypool/pkgA#{package_pr_number} merged.")
|
||||
|
||||
if not project_merged:
|
||||
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
|
||||
if prj_details.get("merged"):
|
||||
project_merged = True
|
||||
print(f"Project PR myproducts/mySLFO#{project_pr_number} merged.")
|
||||
|
||||
if package_merged and project_merged:
|
||||
break
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
assert not package_merged, f"Package PR mypool/pkgA#{package_pr_number} was merged after 'merge ok'."
|
||||
assert not project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was merged after 'merge ok'."
|
||||
print("Both PRs merged not after 'merge ok'.")
|
||||
|
||||
@pytest.mark.t008
|
||||
def test_008_merge_mode_ff_only_success(merge_ff_env, test_user_client):
|
||||
"""
|
||||
|
||||
@@ -139,7 +139,6 @@ index 0000000..e69de29
|
||||
|
||||
|
||||
@pytest.mark.t005
|
||||
# @pytest.mark.xfail(reason="TBD troubleshoot")
|
||||
def test_005_any_maintainer_approval_sufficient(maintainer_env, ownerA_client, ownerBB_client):
|
||||
"""
|
||||
Test scenario:
|
||||
@@ -201,7 +200,6 @@ index 0000000..e69de29
|
||||
|
||||
|
||||
@pytest.mark.t006
|
||||
@pytest.mark.xfail(reason="tbd flacky in ci")
|
||||
def test_006_maintainer_rejection_removes_other_requests(maintainer_env, ownerA_client, ownerBB_client):
|
||||
"""
|
||||
Test scenario:
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
@@ -734,16 +733,8 @@ func ProcessQaProjects(obs common.ObsClientInterface, stagingConfig *common.Stag
|
||||
var qa_projects []string
|
||||
for _, setup := range stagingConfig.QA {
|
||||
QAproject := stagingProject + ":" + setup.Name
|
||||
if len(setup.Labels) > 0 {
|
||||
matchedLabel := false
|
||||
for labelName := range prLabelNames {
|
||||
if slices.Contains(setup.Labels, labelName) {
|
||||
matchedLabel = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matchedLabel == false {
|
||||
if len(setup.Label) > 0 {
|
||||
if _, ok := prLabelNames[setup.Label]; !ok {
|
||||
if !IsDryRun {
|
||||
// blindly remove, will fail when not existing
|
||||
obs.DeleteProject(QAproject)
|
||||
@@ -919,13 +910,10 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
if !stagingConfig.RebuildAll {
|
||||
for pkg, headOid := range headSubmodules {
|
||||
if baseOid, exists := baseSubmodules[pkg]; !exists || baseOid != headOid {
|
||||
if pkg != "rpms" && pkg != "dependencies" {
|
||||
_, spkg := filepath.Split(pkg)
|
||||
if exists {
|
||||
modifiedPackages = append(modifiedPackages, spkg)
|
||||
modifiedPackages = append(modifiedPackages, pkg)
|
||||
} else {
|
||||
newPackages = append(newPackages, spkg)
|
||||
}
|
||||
newPackages = append(newPackages, pkg)
|
||||
}
|
||||
common.LogDebug(pkg, ":", baseOid, "->", headOid)
|
||||
}
|
||||
@@ -1035,7 +1023,6 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
done := false
|
||||
overallBuildStatus := ProcessBuildStatus(stagingResult)
|
||||
commentSuffix := ""
|
||||
common.LogDebug("Number of QA Projects: ", len(qaProjects))
|
||||
if len(qaProjects) > 0 && overallBuildStatus == BuildStatusSummarySuccess {
|
||||
seperator := " in "
|
||||
for _, qaProject := range qaProjects {
|
||||
@@ -1184,7 +1171,6 @@ var IsDryRun bool
|
||||
var ProcessPROnly string
|
||||
var ObsClient common.ObsClientInterface
|
||||
var BotUser string
|
||||
var PollInterval = 5 * time.Minute
|
||||
|
||||
func ObsWebHostFromApiHost(apihost string) string {
|
||||
u, err := url.Parse(apihost)
|
||||
@@ -1207,18 +1193,9 @@ func main() {
|
||||
flag.StringVar(&ObsApiHost, "obs", "", "API for OBS instance")
|
||||
flag.StringVar(&ObsWebHost, "obs-web", "", "Web OBS instance, if not derived from the obs config")
|
||||
flag.BoolVar(&IsDryRun, "dry", false, "Dry-run, don't actually create any build projects or review changes")
|
||||
pollIntervalStr := flag.String("poll-interval", common.GetEnvOverrideString(os.Getenv("AUTOGITS_STAGING_BOT_POLL_INTERVAL"), ""), "Polling interval for notifications (e.g. 5m, 10s)")
|
||||
debug := flag.Bool("debug", false, "Turns on debug logging")
|
||||
flag.Parse()
|
||||
|
||||
if len(*pollIntervalStr) > 0 {
|
||||
if d, err := time.ParseDuration(*pollIntervalStr); err == nil {
|
||||
PollInterval = d
|
||||
} else {
|
||||
common.LogError("Invalid poll interval:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if *debug {
|
||||
common.SetLoggingLevel(common.LogLevelDebug)
|
||||
} else {
|
||||
@@ -1287,6 +1264,6 @@ func main() {
|
||||
for {
|
||||
PollWorkNotifications(ObsClient, gitea)
|
||||
common.LogInfo("Poll cycle finished")
|
||||
time.Sleep(PollInterval)
|
||||
time.Sleep(5 * time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,9 @@ After=network-online.target
|
||||
Type=exec
|
||||
ExecStart=/usr/bin/obs-staging-bot
|
||||
EnvironmentFile=-/etc/default/obs-staging-bot.env
|
||||
User=autogits_obs_staging_bot
|
||||
Group=users
|
||||
|
||||
# This may work when not using ssh api connections:
|
||||
#DynamicUser=yes
|
||||
#NoNewPrivileges=yes
|
||||
#ProtectSystem=strict
|
||||
DynamicUser=yes
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=strict
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -123,7 +123,7 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
|
||||
common.LogError(" - ", action.Repository.Name, "repo is not sha256. Ignoring.")
|
||||
return
|
||||
}
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + action.Repository.Name, action.Repository.Name))
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name))
|
||||
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||
|
||||
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 {
|
||||
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + action.Repository.Name, action.Repository.Name)
|
||||
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--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.")
|
||||
@@ -420,7 +420,7 @@ next_repo:
|
||||
}
|
||||
|
||||
// add repository to git project
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + r.Name, r.Name))
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", r.CloneURL, r.Name))
|
||||
|
||||
curBranch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
|
||||
if branch != curBranch {
|
||||
|
||||
@@ -54,7 +54,6 @@ This is the ProjectGit config file. For runtime config file, see bottom.
|
||||
| *GitProjectName* | Repository and branch where the ProjectGit lives. | no | string | **Format**: `org/project_repo#branch` | By default assumes `_ObsPrj` with default branch in the *Organization* |
|
||||
| *ManualMergeOnly* | Merges are permitted only upon receiving a "merge ok" comment from designated maintainers in the PkgGit PR. | no | bool | true, false | false |
|
||||
| *ManualMergeProject* | Merges are permitted only upon receiving a "merge ok" comment in the ProjectGit PR from project maintainers. | no | bool | true, false | false |
|
||||
| *MergeMode* | Type of package merge accepted. See below for details. | no | string | ff-only, replace, devel | ff-only |
|
||||
| *ReviewRequired* | If submitter is a maintainer, require review from another maintainer if available. | no | bool | true, false | false |
|
||||
| *NoProjectGitPR* | Do not create PrjGit PR, but still perform other tasks. | no | bool | true, false | false |
|
||||
| *Reviewers* | PrjGit reviewers. Additional review requests are triggered for associated PkgGit PRs. PrjGit PR is merged only when all reviews are complete. | no | array of strings | | `[]` |
|
||||
@@ -118,6 +117,8 @@ The following labels are used, when defined in Repo/Org.
|
||||
| Label Config Entry | Default label | Description
|
||||
|--------------------|----------------|----------------------------------------
|
||||
| StagingAuto | staging/Auto | Assigned to Project Git PRs when first staged
|
||||
| ReviewPending | review/Pending | Assigned to Project Git PR when package reviews are still pending
|
||||
| ReviewDone | review/Done | Assigned to Project Git PR when reviews are complete on all package PRs
|
||||
|
||||
|
||||
Maintainership
|
||||
|
||||
@@ -180,33 +180,6 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
|
||||
}
|
||||
|
||||
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)
|
||||
common.LogDebug("status:", status)
|
||||
common.LogDebug("submodule", repo, " hash:", id, " -> ", prHead)
|
||||
@@ -341,7 +314,6 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
// well, wrong place...
|
||||
// common.LogError("Warning: source and target branch are in different repositories. We may not have the right permissions...")
|
||||
// Gitea.AddComment(PrjGitPR.PR, "This PR does not allow maintainer changes, but referenced package branch has changed!")
|
||||
common.LogError("Warning: source and target branch are in different repositories. We may not have the right permissions...")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -412,7 +384,7 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
}
|
||||
return CurrentTitle == NewTitle
|
||||
}
|
||||
if !pr.config.NoProjectGitPR && PrjGitPR.PR.User.UserName == CurrentUser.UserName && (PrjGitPR.PR.Body != PrjGitBody || !isPrTitleSame(PrjGitPR.PR.Title, PrjGitTitle)) {
|
||||
if 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{
|
||||
RemoveDeadline: true,
|
||||
Title: PrjGitTitle,
|
||||
|
||||
Reference in New Issue
Block a user