Compare commits
3 Commits
direct-fix
...
submodulem
| Author | SHA256 | Date | |
|---|---|---|---|
| 66a2c0565e | |||
| d5d6910906 | |||
| 444959540a |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
*.osc
|
*.osc
|
||||||
*.conf
|
*.conf
|
||||||
|
utils/gitmodules-automerge/gitmodules-automerge
|
||||||
|
utils/hujson/hujson
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ Provides: /usr/bin/hujson
|
|||||||
|
|
||||||
%description utils
|
%description utils
|
||||||
HuJSON to JSON parser, using stdin -> stdout pipe
|
HuJSON to JSON parser, using stdin -> stdout pipe
|
||||||
|
gitmodules-automerge fixes conflicts in .gitmodules conflicts during merge
|
||||||
|
|
||||||
|
|
||||||
%package workflow-direct
|
%package workflow-direct
|
||||||
@@ -127,6 +128,9 @@ go build \
|
|||||||
go build \
|
go build \
|
||||||
-C utils/hujson \
|
-C utils/hujson \
|
||||||
-buildmode=pie
|
-buildmode=pie
|
||||||
|
go build \
|
||||||
|
-C utils/gitmodules-automerge \
|
||||||
|
-buildmode=pie
|
||||||
go build \
|
go build \
|
||||||
-C gitea-events-rabbitmq-publisher \
|
-C gitea-events-rabbitmq-publisher \
|
||||||
-buildmode=pie
|
-buildmode=pie
|
||||||
@@ -173,9 +177,9 @@ install -D -m0644 systemd/obs-staging-bot.service
|
|||||||
install -D -m0755 obs-status-service/obs-status-service %{buildroot}%{_bindir}/obs-status-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 -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 -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 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr
|
||||||
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
|
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
|
||||||
|
install -D -m0755 utils/gitmodules-automerge/gitmodules-automerge %{buildroot}%{_bindir}/gitmodules-automerge
|
||||||
|
|
||||||
%pre gitea-events-rabbitmq-publisher
|
%pre gitea-events-rabbitmq-publisher
|
||||||
%service_add_pre gitea-events-rabbitmq-publisher.service
|
%service_add_pre gitea-events-rabbitmq-publisher.service
|
||||||
@@ -257,12 +261,12 @@ install -D -m0755 utils/hujson/hujson
|
|||||||
%files utils
|
%files utils
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%{_bindir}/hujson
|
%{_bindir}/hujson
|
||||||
|
%{_bindir}/gitmodules-automerge
|
||||||
|
|
||||||
%files workflow-direct
|
%files workflow-direct
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%doc workflow-direct/README.md
|
%doc workflow-direct/README.md
|
||||||
%{_bindir}/workflow-direct
|
%{_bindir}/workflow-direct
|
||||||
%{_unitdir}/workflow-direct@.service
|
|
||||||
|
|
||||||
%files workflow-pr
|
%files workflow-pr
|
||||||
%license COPYING
|
%license COPYING
|
||||||
|
|||||||
296
common/git_parser.go
Normal file
296
common/git_parser.go
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GitStatus_Untracked = 0
|
||||||
|
GitStatus_Modified = 1
|
||||||
|
GitStatus_Ignored = 2
|
||||||
|
GitStatus_Unmerged = 3 // States[0..3] -- Stage1, Stage2, Stage3 of merge objects
|
||||||
|
GitStatus_Renamed = 4 // orig name in States[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
type GitStatusData struct {
|
||||||
|
Path string
|
||||||
|
Status int
|
||||||
|
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 parseGit_HexString(data io.ByteReader) (string, error) {
|
||||||
|
str := make([]byte, 0, 32)
|
||||||
|
for {
|
||||||
|
c, err := data.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case c == 0 || c == ' ':
|
||||||
|
return string(str), nil
|
||||||
|
case c >= 'a' && c <= 'f':
|
||||||
|
case c >= 'A' && c <= 'F':
|
||||||
|
case c >= '0' && c <= '9':
|
||||||
|
default:
|
||||||
|
return "", errors.New("Invalid character in hex string:" + string(c))
|
||||||
|
}
|
||||||
|
str = append(str, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func parseGit_String(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 parseGit_StringWithSpace(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 {
|
||||||
|
return string(str), nil
|
||||||
|
}
|
||||||
|
str = append(str, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipGitStatusEntry(data io.ByteReader, skipSpaceLen int) error {
|
||||||
|
for skipSpaceLen > 0 {
|
||||||
|
c, err := data.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c == ' ' {
|
||||||
|
skipSpaceLen--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
|
||||||
|
ret := GitStatusData{}
|
||||||
|
statusType, err := data.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
switch statusType {
|
||||||
|
case '1':
|
||||||
|
var err error
|
||||||
|
if err = skipGitStatusEntry(data, 8); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret.Status = GitStatus_Modified
|
||||||
|
ret.Path, err = parseGit_StringWithSpace(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case '2':
|
||||||
|
var err error
|
||||||
|
if err = skipGitStatusEntry(data, 9); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret.Status = GitStatus_Renamed
|
||||||
|
ret.Path, err = parseGit_StringWithSpace(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret.States[0], err = parseGit_StringWithSpace(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case '?':
|
||||||
|
var err error
|
||||||
|
if err = skipGitStatusEntry(data, 1); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret.Status = GitStatus_Untracked
|
||||||
|
ret.Path, err = parseGit_StringWithSpace(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case '!':
|
||||||
|
var err error
|
||||||
|
if err = skipGitStatusEntry(data, 1); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret.Status = GitStatus_Ignored
|
||||||
|
ret.Path, err = parseGit_StringWithSpace(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case 'u':
|
||||||
|
var err error
|
||||||
|
if err = skipGitStatusEntry(data, 2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ret.SubmoduleChanges, err = parseGit_String(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = skipGitStatusEntry(data, 4); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret.States[0], err = parseGit_HexString(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ret.States[1], err = parseGit_HexString(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ret.States[2], err = parseGit_HexString(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret.Status = GitStatus_Unmerged
|
||||||
|
ret.Path, err = parseGit_StringWithSpace(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Invalid status type" + string(statusType))
|
||||||
|
}
|
||||||
|
return &ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGitStatusData(data io.ByteReader) (Data, error) {
|
||||||
|
ret := make([]GitStatusData, 0, 10)
|
||||||
|
for {
|
||||||
|
data, err := parseSingleStatusEntry(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if data == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, *data)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Data interface{}
|
||||||
|
|
||||||
|
type CommitStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Add CommitStatus = iota
|
||||||
|
Rm
|
||||||
|
Copy
|
||||||
|
Modify
|
||||||
|
Rename
|
||||||
|
TypeChange
|
||||||
|
Unmerged
|
||||||
|
Unknown
|
||||||
|
)
|
||||||
|
|
||||||
|
type GitDiffRawData struct {
|
||||||
|
SrcMode, DstMode string
|
||||||
|
SrcCommit, DstCommit string
|
||||||
|
Status CommitStatus
|
||||||
|
Src, Dst string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGit_DiffIndexStatus(data io.ByteReader, d *GitDiffRawData) error {
|
||||||
|
b, err := data.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch b {
|
||||||
|
case 'A':
|
||||||
|
d.Status = Add
|
||||||
|
case 'C':
|
||||||
|
d.Status = Copy
|
||||||
|
case 'D':
|
||||||
|
d.Status = Rm
|
||||||
|
case 'M':
|
||||||
|
d.Status = Modify
|
||||||
|
case 'R':
|
||||||
|
d.Status = Rename
|
||||||
|
case 'T':
|
||||||
|
d.Status = TypeChange
|
||||||
|
case 'U':
|
||||||
|
d.Status = Unmerged
|
||||||
|
case 'X':
|
||||||
|
return fmt.Errorf("Unexpected unknown change type. This is a git bug")
|
||||||
|
}
|
||||||
|
_, err = parseGit_StringWithSpace(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSingleGitDiffIndexRawData(data io.ByteReader) (*GitDiffRawData, error) {
|
||||||
|
var ret GitDiffRawData
|
||||||
|
|
||||||
|
b, err := data.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if b != ':' {
|
||||||
|
return nil, fmt.Errorf("Expected ':' but got '%s'", string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret.SrcMode, err = parseGit_String(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ret.DstMode, err = parseGit_String(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ret.Src, err = parseGit_String(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ret.Dst, err = parseGit_String(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = parseGit_DiffIndexStatus(data, &ret); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret.Dst = ret.Src
|
||||||
|
switch ret.Status {
|
||||||
|
case Copy, Rename:
|
||||||
|
if ret.Src, err = parseGit_StringWithSpace(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGitDiffIndexRawData(data io.ByteReader) (Data, error) {
|
||||||
|
ret := make([]GitDiffRawData, 0, 10)
|
||||||
|
for {
|
||||||
|
data, err := parseSingleGitDiffIndexRawData(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if data == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, *data)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
@@ -19,9 +19,7 @@ package common
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -44,6 +42,11 @@ type GitDirectoryLister interface {
|
|||||||
GitDirectoryList(gitPath, commitId string) (dirlist map[string]string, err error)
|
GitDirectoryList(gitPath, commitId string) (dirlist map[string]string, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GitSubmoduleFileConflictResolver interface {
|
||||||
|
GitResolveConflicts(cwd, MergeBase, Head, MergeHead string) error
|
||||||
|
GitResolveSubmoduleFileConflict(s GitStatusData, cwd, mergeBase, head, mergeHead string) error
|
||||||
|
}
|
||||||
|
|
||||||
type GitStatusLister interface {
|
type GitStatusLister interface {
|
||||||
GitStatus(cwd string) ([]GitStatusData, error)
|
GitStatus(cwd string) ([]GitStatusData, error)
|
||||||
}
|
}
|
||||||
@@ -75,6 +78,7 @@ type Git interface {
|
|||||||
GitExecQuietOrPanic(cwd string, params ...string)
|
GitExecQuietOrPanic(cwd string, params ...string)
|
||||||
|
|
||||||
GitDiffLister
|
GitDiffLister
|
||||||
|
GitSubmoduleFileConflictResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
type GitHandlerImpl struct {
|
type GitHandlerImpl struct {
|
||||||
@@ -353,13 +357,20 @@ func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string
|
|||||||
cmd.Env = []string{
|
cmd.Env = []string{
|
||||||
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
|
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
|
||||||
"GIT_CONFIG_GLOBAL=/dev/null",
|
"GIT_CONFIG_GLOBAL=/dev/null",
|
||||||
"GIT_AUTHOR_NAME=" + e.GitCommiter,
|
|
||||||
"GIT_COMMITTER_NAME=" + e.GitCommiter,
|
|
||||||
"EMAIL=not@exist@src.opensuse.org",
|
|
||||||
"GIT_LFS_SKIP_SMUDGE=1",
|
"GIT_LFS_SKIP_SMUDGE=1",
|
||||||
"GIT_LFS_SKIP_PUSH=1",
|
"GIT_LFS_SKIP_PUSH=1",
|
||||||
"GIT_SSH_COMMAND=/usr/bin/ssh -o StrictHostKeyChecking=yes",
|
"GIT_SSH_COMMAND=/usr/bin/ssh -o StrictHostKeyChecking=yes",
|
||||||
}
|
}
|
||||||
|
if len(e.GitEmail) > 0 {
|
||||||
|
cmd.Env = append(cmd.Env, "EMAIL="+e.GitEmail)
|
||||||
|
}
|
||||||
|
if len(e.GitCommiter) > 0 {
|
||||||
|
cmd.Env = append(cmd.Env,
|
||||||
|
"GIT_AUTHOR_NAME="+e.GitCommiter,
|
||||||
|
"GIT_COMMITTER_NAME="+e.GitCommiter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if len(ExtraGitParams) > 0 {
|
if len(ExtraGitParams) > 0 {
|
||||||
cmd.Env = append(cmd.Env, ExtraGitParams...)
|
cmd.Env = append(cmd.Env, ExtraGitParams...)
|
||||||
}
|
}
|
||||||
@@ -1002,193 +1013,10 @@ func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string)
|
|||||||
return subCommitId, len(subCommitId) > 0
|
return subCommitId, len(subCommitId) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
func (e *GitHandlerImpl) GitExecWithDataParse(cwd string, dataprocessor func(io.ByteReader) (Data, error), gitcmd string, args ...string) (Data, error) {
|
||||||
GitStatus_Untracked = 0
|
LogDebug("getting", gitcmd)
|
||||||
GitStatus_Modified = 1
|
args = append([]string{gitcmd}, args...)
|
||||||
GitStatus_Ignored = 2
|
cmd := exec.Command("/usr/bin/git", args...)
|
||||||
GitStatus_Unmerged = 3 // States[0..3] -- Stage1, Stage2, Stage3 of merge objects
|
|
||||||
GitStatus_Renamed = 4 // orig name in States[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
type GitStatusData struct {
|
|
||||||
Path string
|
|
||||||
Status int
|
|
||||||
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) {
|
|
||||||
str := make([]byte, 0, 32)
|
|
||||||
for {
|
|
||||||
c, err := data.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case c == 0 || c == ' ':
|
|
||||||
return string(str), nil
|
|
||||||
case c >= 'a' && c <= 'f':
|
|
||||||
case c >= 'A' && c <= 'F':
|
|
||||||
case c >= '0' && c <= '9':
|
|
||||||
default:
|
|
||||||
return "", errors.New("Invalid character in hex string:" + string(c))
|
|
||||||
}
|
|
||||||
str = append(str, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
for {
|
|
||||||
c, err := data.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.New("Unexpected EOF. Expected NUL string term")
|
|
||||||
}
|
|
||||||
if c == 0 {
|
|
||||||
return string(str), nil
|
|
||||||
}
|
|
||||||
str = append(str, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func skipGitStatusEntry(data io.ByteReader, skipSpaceLen int) error {
|
|
||||||
for skipSpaceLen > 0 {
|
|
||||||
c, err := data.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c == ' ' {
|
|
||||||
skipSpaceLen--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
|
|
||||||
ret := GitStatusData{}
|
|
||||||
statusType, err := data.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
switch statusType {
|
|
||||||
case '1':
|
|
||||||
var err error
|
|
||||||
if err = skipGitStatusEntry(data, 8); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret.Status = GitStatus_Modified
|
|
||||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case '2':
|
|
||||||
var err error
|
|
||||||
if err = skipGitStatusEntry(data, 9); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret.Status = GitStatus_Renamed
|
|
||||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret.States[0], err = parseGitStatusStringWithSpace(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case '?':
|
|
||||||
var err error
|
|
||||||
if err = skipGitStatusEntry(data, 1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret.Status = GitStatus_Untracked
|
|
||||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case '!':
|
|
||||||
var err error
|
|
||||||
if err = skipGitStatusEntry(data, 1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret.Status = GitStatus_Ignored
|
|
||||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case 'u':
|
|
||||||
var err error
|
|
||||||
if err = skipGitStatusEntry(data, 2); err != nil {
|
|
||||||
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 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ret.States[1], err = parseGitStatusHexString(data); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ret.States[2], err = parseGitStatusHexString(data); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret.Status = GitStatus_Unmerged
|
|
||||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("Invalid status type" + string(statusType))
|
|
||||||
}
|
|
||||||
return &ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseGitStatusData(data io.ByteReader) ([]GitStatusData, error) {
|
|
||||||
ret := make([]GitStatusData, 0, 10)
|
|
||||||
for {
|
|
||||||
data, err := parseSingleStatusEntry(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if data == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = append(ret, *data)
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *GitHandlerImpl) GitStatus(cwd string) (ret []GitStatusData, err error) {
|
|
||||||
LogDebug("getting git-status()")
|
|
||||||
|
|
||||||
cmd := exec.Command("/usr/bin/git", "status", "--porcelain=2", "-z")
|
|
||||||
cmd.Env = []string{
|
cmd.Env = []string{
|
||||||
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
|
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
|
||||||
"GIT_LFS_SKIP_SMUDGE=1",
|
"GIT_LFS_SKIP_SMUDGE=1",
|
||||||
@@ -1205,7 +1033,12 @@ func (e *GitHandlerImpl) GitStatus(cwd string) (ret []GitStatusData, err error)
|
|||||||
LogError("Error running command", cmd.Args, err)
|
LogError("Error running command", cmd.Args, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseGitStatusData(bufio.NewReader(bytes.NewReader(out)))
|
return dataprocessor(bytes.NewReader(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *GitHandlerImpl) GitStatus(cwd string) (ret []GitStatusData, err error) {
|
||||||
|
data, err := e.GitExecWithDataParse(cwd, parseGitStatusData, "status", "--porcelain=2", "-z")
|
||||||
|
return data.([]GitStatusData), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *GitHandlerImpl) GitDiff(cwd, base, head string) (string, error) {
|
func (e *GitHandlerImpl) GitDiff(cwd, base, head string) (string, error) {
|
||||||
@@ -1230,3 +1063,122 @@ func (e *GitHandlerImpl) GitDiff(cwd, base, head string) (string, error) {
|
|||||||
|
|
||||||
return string(out), nil
|
return string(out), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *GitHandlerImpl) GitDiffIndex(cwd, commit string) ([]GitDiffRawData, error) {
|
||||||
|
data, err := e.GitExecWithDataParse("diff-index", parseGitDiffIndexRawData, cwd, "diff-index", "-z", "--raw", "--full-index", "--submodule=short", "HEAD")
|
||||||
|
return data.([]GitDiffRawData), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (git *GitHandlerImpl) GitResolveConflicts(cwd, mergeBase, head, mergeHead string) error {
|
||||||
|
status, err := git.GitStatus(cwd)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Status failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can only resolve conflicts with .gitmodules
|
||||||
|
for _, s := range status {
|
||||||
|
if s.Status == GitStatus_Unmerged && s.Path == ".gitmodules" {
|
||||||
|
if err := git.GitResolveSubmoduleFileConflict(s, cwd, mergeBase, head, mergeHead); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if s.Status == GitStatus_Unmerged {
|
||||||
|
return fmt.Errorf("Cannot automatically resolve conflict: %s", s.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return git.GitExec(cwd, "-c", "core.editor=true", "merge", "--continue")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (git *GitHandlerImpl) GitResolveSubmoduleFileConflict(s GitStatusData, cwd, mergeBase, head, mergeHead string) error {
|
||||||
|
submodules1, err := git.GitSubmoduleList(cwd, mergeBase)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to fetch submodules during merge resolution: %w", err)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
submodules2, err := git.GitSubmoduleList(cwd, head)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to fetch submodules during merge resolution: %w", err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
submodules3, err := git.GitSubmoduleList(cwd, mergeHead)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to fetch submodules during merge resolution: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// find modified submodules in the mergeHead
|
||||||
|
modifiedSubmodules := make([]string, 0, 10)
|
||||||
|
removedSubmodules := make([]string, 0, 10)
|
||||||
|
addedSubmodules := make([]string, 0, 10)
|
||||||
|
|
||||||
|
for submodulePath, oldHash := range submodules1 {
|
||||||
|
if newHash, found := submodules3[submodulePath]; found && newHash != oldHash {
|
||||||
|
modifiedSubmodules = append(modifiedSubmodules, submodulePath)
|
||||||
|
} else if !found {
|
||||||
|
removedSubmodules = append(removedSubmodules, submodulePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for submodulePath, _ := range submodules3 {
|
||||||
|
if _, found := submodules1[submodulePath]; !found {
|
||||||
|
addedSubmodules = append(addedSubmodules, submodulePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to adjust the `submodules` list by the pending changes to the index
|
||||||
|
s1, err := git.GitExecWithOutput(cwd, "cat-file", "blob", s.States[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
||||||
|
}
|
||||||
|
s2, err := git.GitExecWithOutput(cwd, "cat-file", "blob", s.States[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
||||||
|
}
|
||||||
|
s3, err := git.GitExecWithOutput(cwd, "cat-file", "blob", s.States[2])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ParseSubmodulesFile(strings.NewReader(s1))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
||||||
|
}
|
||||||
|
subs2, err := ParseSubmodulesFile(strings.NewReader(s2))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
||||||
|
}
|
||||||
|
subs3, err := ParseSubmodulesFile(strings.NewReader(s3))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
overrideSet := make([]Submodule, 0, len(addedSubmodules)+len(modifiedSubmodules))
|
||||||
|
for i := range subs3 {
|
||||||
|
if slices.Contains(addedSubmodules, subs3[i].Path) || slices.Contains(modifiedSubmodules, subs3[i].Path) {
|
||||||
|
overrideSet = append(overrideSet, subs3[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// merge from subs1 (merge-base), subs2 (changes in base since merge-base HEAD), subs3 (merge_source MERGE_HEAD)
|
||||||
|
// this will update submodules
|
||||||
|
SubmoduleCompare := func(a, b Submodule) int { return strings.Compare(a.Path, b.Path) }
|
||||||
|
CompactCompare := func(a, b Submodule) bool { return a.Path == b.Path }
|
||||||
|
|
||||||
|
// remove submodules that are removed in the PR
|
||||||
|
subs2 = slices.DeleteFunc(subs2, func(a Submodule) bool { return slices.Contains(removedSubmodules, a.Path) })
|
||||||
|
|
||||||
|
mergedSubs := slices.Concat(overrideSet, subs2)
|
||||||
|
slices.SortStableFunc(mergedSubs, SubmoduleCompare)
|
||||||
|
filteredSubs := slices.CompactFunc(mergedSubs, CompactCompare)
|
||||||
|
|
||||||
|
out, err := os.Create(path.Join(git.GetPath(), cwd, ".gitmodules"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Can't open .gitmodules for writing: %w", err)
|
||||||
|
}
|
||||||
|
if err = WriteSubmodules(filteredSubs, out); err != nil {
|
||||||
|
return fmt.Errorf("Can't write .gitmodules: %w", err)
|
||||||
|
}
|
||||||
|
if out.Close(); err != nil {
|
||||||
|
return fmt.Errorf("Can't close .gitmodules: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
git.GitExecOrPanic(cwd, "add", ".gitmodules")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
|
"runtime/debug"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -93,6 +94,145 @@ func TestGitClone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSubmoduleConflictResolution(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
checkout, merge string
|
||||||
|
result string
|
||||||
|
merge_fail bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "adding two submodules",
|
||||||
|
checkout: "base_add_b1",
|
||||||
|
merge: "base_add_b2",
|
||||||
|
result: `[submodule "pkgA"]
|
||||||
|
path = pkgA
|
||||||
|
url = ../pkgA
|
||||||
|
[submodule "pkgB"]
|
||||||
|
path = pkgB
|
||||||
|
url = ../pkgB
|
||||||
|
[submodule "pkgB1"]
|
||||||
|
path = pkgB1
|
||||||
|
url = ../pkgB1
|
||||||
|
[submodule "pkgB2"]
|
||||||
|
path = pkgB2
|
||||||
|
url = ../pkgB2
|
||||||
|
[submodule "pkgC"]
|
||||||
|
path = pkgC
|
||||||
|
url = ../pkgC
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove one module and add another",
|
||||||
|
checkout: "base_rm_c",
|
||||||
|
merge: "base_add_b2",
|
||||||
|
result: `[submodule "pkgA"]
|
||||||
|
path = pkgA
|
||||||
|
url = ../pkgA
|
||||||
|
[submodule "pkgB"]
|
||||||
|
path = pkgB
|
||||||
|
url = ../pkgB
|
||||||
|
[submodule "pkgB2"]
|
||||||
|
path = pkgB2
|
||||||
|
url = ../pkgB2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add one and remove another",
|
||||||
|
checkout: "base_add_b2",
|
||||||
|
merge: "base_rm_c",
|
||||||
|
result: `[submodule "pkgA"]
|
||||||
|
path = pkgA
|
||||||
|
url = ../pkgA
|
||||||
|
[submodule "pkgB"]
|
||||||
|
path = pkgB
|
||||||
|
url = ../pkgB
|
||||||
|
[submodule "pkgB2"]
|
||||||
|
path = pkgB2
|
||||||
|
url = ../pkgB2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rm modified submodule",
|
||||||
|
checkout: "base_modify_c",
|
||||||
|
merge: "base_rm_c",
|
||||||
|
merge_fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "modified removed submodule",
|
||||||
|
checkout: "base_rm_c",
|
||||||
|
merge: "base_modify_c",
|
||||||
|
merge_fail: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := os.MkdirTemp(os.TempDir(), "submoduletests")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cmd := exec.Command(cwd + "/test_repo_setup.sh")
|
||||||
|
cmd.Dir = d
|
||||||
|
_, err = cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gh, err := AllocateGitWorkTree(d, "test", "foo@example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
success := true
|
||||||
|
noErrorOrFail := func(t *testing.T, err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(string(debug.Stack()), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
success = t.Run(test.name, func(t *testing.T) {
|
||||||
|
git, err := gh.ReadExistingPath("prjgit")
|
||||||
|
defer git.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
noErrorOrFail(t, git.GitExec("", "reset", "--hard"))
|
||||||
|
noErrorOrFail(t, git.GitExec("", "checkout", "-B", "test", test.checkout))
|
||||||
|
// noErrorOrFail(t, git.GitExec("", "merge", test.checkout))
|
||||||
|
err = git.GitExec("", "merge", test.merge)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected a conflict")
|
||||||
|
}
|
||||||
|
err = git.GitResolveConflicts("", "main", test.checkout, test.merge)
|
||||||
|
if err != nil {
|
||||||
|
if test.merge_fail {
|
||||||
|
return // success
|
||||||
|
}
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if test.merge_fail {
|
||||||
|
t.Fatal("Expected fail but succeeded?")
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(git.GetPath() + "/.gitmodules")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Cannot read .gitmodules.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(data) != test.result {
|
||||||
|
t.Error("Expected", len(test.result), test.result, "but have", len(data), string(data))
|
||||||
|
}
|
||||||
|
}) && success
|
||||||
|
}
|
||||||
|
|
||||||
|
if success {
|
||||||
|
os.RemoveAll(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGitMsgParsing(t *testing.T) {
|
func TestGitMsgParsing(t *testing.T) {
|
||||||
t.Run("tree message with size 56", func(t *testing.T) {
|
t.Run("tree message with size 56", func(t *testing.T) {
|
||||||
const hdr = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f98 tree 56\x00"
|
const hdr = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f98 tree 56\x00"
|
||||||
@@ -584,12 +724,15 @@ func TestGitStatusParse(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(r) != len(test.res) {
|
|
||||||
t.Fatal("len(r):", len(r), "is not expected", len(test.res))
|
res := r.([]GitStatusData)
|
||||||
|
|
||||||
|
if len(res) != len(test.res) {
|
||||||
|
t.Fatal("len(r):", len(res), "is not expected", len(test.res))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, expected := range test.res {
|
for _, expected := range test.res {
|
||||||
if !slices.Contains(r, expected) {
|
if !slices.Contains(res, expected) {
|
||||||
t.Fatal("result", r, "doesn't contains expected", expected)
|
t.Fatal("result", r, "doesn't contains expected", expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
78
common/pr.go
78
common/pr.go
@@ -4,8 +4,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -433,80 +431,8 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
|||||||
|
|
||||||
err = git.GitExec(DefaultGitPrj, "merge", "--no-ff", "-m", msg, prjgit.Head.Sha)
|
err = git.GitExec(DefaultGitPrj, "merge", "--no-ff", "-m", msg, prjgit.Head.Sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
status, statusErr := git.GitStatus(DefaultGitPrj)
|
if resolveError := git.GitResolveConflicts(DefaultGitPrj, prjgit.MergeBase, prjgit.Base.Sha, prjgit.Head.Sha); resolveError != nil {
|
||||||
if statusErr != nil {
|
return fmt.Errorf("Merge failed. (%w): %w", err, resolveError)
|
||||||
return fmt.Errorf("Failed to merge: %w . Status also failed: %w", err, statusErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we can only resolve conflicts with .gitmodules
|
|
||||||
for _, s := range status {
|
|
||||||
if s.Status == GitStatus_Unmerged {
|
|
||||||
panic("Can't handle conflicts yet")
|
|
||||||
if s.Path != ".gitmodules" {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
submodules, err := git.GitSubmoduleList(DefaultGitPrj, "MERGE_HEAD")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to fetch submodules during merge resolution: %w", err)
|
|
||||||
}
|
|
||||||
s1, err := git.GitExecWithOutput(DefaultGitPrj, "cat-file", "blob", s.States[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
|
||||||
}
|
|
||||||
s2, err := git.GitExecWithOutput(DefaultGitPrj, "cat-file", "blob", s.States[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
|
||||||
}
|
|
||||||
s3, err := git.GitExecWithOutput(DefaultGitPrj, "cat-file", "blob", s.States[2])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
subs1, err := ParseSubmodulesFile(strings.NewReader(s1))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
|
||||||
}
|
|
||||||
subs2, err := ParseSubmodulesFile(strings.NewReader(s2))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
|
||||||
}
|
|
||||||
subs3, err := ParseSubmodulesFile(strings.NewReader(s3))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge from subs3 (target), subs1 (orig), subs2 (2-nd base that is missing from target base)
|
|
||||||
// this will update submodules
|
|
||||||
mergedSubs := slices.Concat(subs1, subs2, subs3)
|
|
||||||
|
|
||||||
var filteredSubs []Submodule = make([]Submodule, 0, max(len(subs1), len(subs2), len(subs3)))
|
|
||||||
nextSub:
|
|
||||||
for subName := range submodules {
|
|
||||||
|
|
||||||
for i := range mergedSubs {
|
|
||||||
if path.Base(mergedSubs[i].Path) == subName {
|
|
||||||
filteredSubs = append(filteredSubs, mergedSubs[i])
|
|
||||||
continue nextSub
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Cannot find submodule for path: %s", subName)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := os.Create(path.Join(git.GetPath(), DefaultGitPrj, ".gitmodules"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Can't open .gitmodules for writing: %w", err)
|
|
||||||
}
|
|
||||||
if err = WriteSubmodules(filteredSubs, out); err != nil {
|
|
||||||
return fmt.Errorf("Can't write .gitmodules: %w", err)
|
|
||||||
}
|
|
||||||
if out.Close(); err != nil {
|
|
||||||
return fmt.Errorf("Can't close .gitmodules: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
git.GitExecOrPanic(DefaultGitPrj, "add", ".gitmodules")
|
|
||||||
git.GitExecOrPanic(DefaultGitPrj, "-c", "core.editor=true", "merge", "--continue")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,22 +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 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))
|
||||||
@@ -940,6 +941,7 @@ func TestPRMerge(t *testing.T) {
|
|||||||
pr: &models.PullRequest{
|
pr: &models.PullRequest{
|
||||||
Base: &models.PRBranchInfo{
|
Base: &models.PRBranchInfo{
|
||||||
Sha: "e8b0de43d757c96a9d2c7101f4bff404e322f53a1fa4041fb85d646110c38ad4", // "base_add_b1"
|
Sha: "e8b0de43d757c96a9d2c7101f4bff404e322f53a1fa4041fb85d646110c38ad4", // "base_add_b1"
|
||||||
|
Name: "master",
|
||||||
Repo: &models.Repository{
|
Repo: &models.Repository{
|
||||||
Name: "prj",
|
Name: "prj",
|
||||||
Owner: &models.User{
|
Owner: &models.User{
|
||||||
@@ -960,6 +962,7 @@ func TestPRMerge(t *testing.T) {
|
|||||||
pr: &models.PullRequest{
|
pr: &models.PullRequest{
|
||||||
Base: &models.PRBranchInfo{
|
Base: &models.PRBranchInfo{
|
||||||
Sha: "4fbd1026b2d7462ebe9229a49100c11f1ad6555520a21ba515122d8bc41328a8",
|
Sha: "4fbd1026b2d7462ebe9229a49100c11f1ad6555520a21ba515122d8bc41328a8",
|
||||||
|
Name: "master",
|
||||||
Repo: &models.Repository{
|
Repo: &models.Repository{
|
||||||
Name: "prj",
|
Name: "prj",
|
||||||
Owner: &models.User{
|
Owner: &models.User{
|
||||||
@@ -979,6 +982,7 @@ func TestPRMerge(t *testing.T) {
|
|||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
ctl := gomock.NewController(t)
|
ctl := gomock.NewController(t)
|
||||||
mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
||||||
|
|
||||||
reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl)
|
reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl)
|
||||||
|
|
||||||
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
|
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
|||||||
@@ -40,10 +40,24 @@ create_prjgit_sample() {
|
|||||||
git submodule -q add ../pkgB2 pkgB2
|
git submodule -q add ../pkgB2 pkgB2
|
||||||
git commit -q -m "pkgB2 added"
|
git commit -q -m "pkgB2 added"
|
||||||
|
|
||||||
git checkout main
|
git checkout -b base_rm_c main
|
||||||
git clean -ffxd
|
git clean -ffxd
|
||||||
git submodule -q add -f ../pkgB1 pkgB1
|
git rm pkgC
|
||||||
git commit -q -m "main adding pkgB1"
|
git commit -q -m 'pkgC removed'
|
||||||
|
|
||||||
|
git checkout -b base_modify_c main
|
||||||
|
git submodule update --init pkgC
|
||||||
|
pushd pkgC
|
||||||
|
echo "mofieid" >> README.md
|
||||||
|
git commit -q -m "modified" README.md
|
||||||
|
popd
|
||||||
|
git commit pkgC -m "modifiedC"
|
||||||
|
git submodule deinit -f pkgC
|
||||||
|
|
||||||
|
# git checkout main
|
||||||
|
# git clean -ffxd
|
||||||
|
# git submodule -q add -f ../pkgB1 pkgB1
|
||||||
|
# git commit -q -m "main adding pkgB1"
|
||||||
|
|
||||||
popd
|
popd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,10 +168,9 @@ func FetchDevelProjects() (DevelProjects, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var DevelProjectNotFound = errors.New("Devel project not found")
|
var DevelProjectNotFound = errors.New("Devel project not found")
|
||||||
|
|
||||||
func (d DevelProjects) GetDevelProject(pkg string) (string, error) {
|
func (d DevelProjects) GetDevelProject(pkg string) (string, error) {
|
||||||
for _, item := range d {
|
for _, item := range d {
|
||||||
if item.Package == pkg {
|
if item.Package == pkg {
|
||||||
return item.Project, nil
|
return item.Project, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,33 +178,3 @@ func (d DevelProjects) GetDevelProject(pkg string) (string, error) {
|
|||||||
return "", DevelProjectNotFound
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -165,58 +165,3 @@ 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,11 +14,15 @@ import (
|
|||||||
"src.opensuse.org/autogits/common"
|
"src.opensuse.org/autogits/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
Context string `json:"context"`
|
||||||
|
State string `json:"state"`
|
||||||
|
TargetUrl string `json:"target_url"`
|
||||||
|
}
|
||||||
|
|
||||||
type StatusInput struct {
|
type StatusInput struct {
|
||||||
Description string `json:"description"`
|
State string `json:"state"`
|
||||||
Context string `json:"context"`
|
TargetUrl string `json:"target_url"`
|
||||||
State string `json:"state"`
|
|
||||||
TargetUrl string `json:"target_url"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -55,26 +59,23 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
config, ok := r.Context().Value(configKey).(*Config)
|
config, ok := r.Context().Value(configKey).(*Config)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
common.LogDebug("Config missing from context")
|
common.LogError("Config missing from context")
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
header := r.Header.Get("Authorization")
|
header := r.Header.Get("Authorization")
|
||||||
if header == "" {
|
if header == "" {
|
||||||
common.LogDebug("Authorization header not found")
|
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
token_arr := strings.Split(header, " ")
|
token_arr := strings.Split(header, " ")
|
||||||
if len(token_arr) != 2 {
|
if len(token_arr) != 2 {
|
||||||
common.LogDebug("Authorization header malformed")
|
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.EqualFold(token_arr[0], "token") {
|
if !strings.EqualFold(token_arr[0], "Bearer") {
|
||||||
common.LogDebug("Token not found in Authorization header")
|
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -82,7 +83,6 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
token := token_arr[1]
|
token := token_arr[1]
|
||||||
|
|
||||||
if !slices.Contains(config.Keys, token) {
|
if !slices.Contains(config.Keys, token) {
|
||||||
common.LogDebug("Provided token is not known")
|
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -104,8 +104,13 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
status := Status{
|
||||||
|
Context: "Build in obs",
|
||||||
|
State: statusinput.State,
|
||||||
|
TargetUrl: statusinput.TargetUrl,
|
||||||
|
}
|
||||||
|
|
||||||
status_payload, err := json.Marshal(statusinput)
|
status_payload, err := json.Marshal(status)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
@@ -126,8 +131,8 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "Content-Type")
|
||||||
req.Header.Add("Authorization", fmt.Sprintf("token %s", ForgeToken))
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", ForgeToken))
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
# gitea_status_proxy
|
|
||||||
|
|
||||||
Allows bots without code owner permission to set Gitea's commit status
|
|
||||||
|
|
||||||
## Basic usage
|
|
||||||
|
|
||||||
To beging, you need the json config and a Gitea token with permissions to the repository you want to write to.
|
|
||||||
|
|
||||||
Keys should be randomly generated, i.e by using openssl: `openssl rand -base64 48`
|
|
||||||
|
|
||||||
Generate a json config file, with the key generated from running the command above, save as example.json:
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"forge_url": "https://src.opensuse.org/api/v1",
|
|
||||||
"keys": ["$YOUR_TOKEN_GOES_HERE"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### start the proxy:
|
|
||||||
|
|
||||||
```
|
|
||||||
GITEA_TOKEN=YOURTOKEN ./gitea_status_proxy -config example.json
|
|
||||||
2025/10/30 12:53:18 [I] server up and listening on :3000
|
|
||||||
```
|
|
||||||
|
|
||||||
Now the proxy should be able to accept requests under: `localhost:3000/repos/{owner}/{repo}/statuses/{sha}`, the token to be used when authenticating to the proxy must be in the `keys` list of the configuration json file (example.json above)
|
|
||||||
|
|
||||||
### example:
|
|
||||||
|
|
||||||
On a separate terminal, you can use curl to post a status to the proxy, if the GITEA_TOKEN has permissions on the target
|
|
||||||
repository, it will result in a new status being set for the given commit
|
|
||||||
|
|
||||||
```
|
|
||||||
curl -X 'POST' \
|
|
||||||
'localhost:3000/repos/szarate/test-actions-gitea/statuses/cd5847c92fb65a628bdd6015f96ee7e569e1ad6e4fc487acc149b52e788262f9' \
|
|
||||||
-H 'accept: application/json' \
|
|
||||||
-H 'Authorization: token $YOUR_TOKEN_GOES_HERE' \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{
|
|
||||||
"context": "Proxy test",
|
|
||||||
"description": "Status posted from the proxy",
|
|
||||||
"state": "success",
|
|
||||||
"target_url": "https://src.opensuse.org"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
After this you should be able to the results in the pull request, e.g from above: https://src.opensuse.org/szarate/test-actions-gitea/pulls/1
|
|
||||||
@@ -386,28 +386,6 @@ func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, git
|
|||||||
}
|
}
|
||||||
// patch baseMeta to become the new project
|
// patch baseMeta to become the new project
|
||||||
templateMeta.Name = stagingProject + ":" + subProjectName
|
templateMeta.Name = stagingProject + ":" + subProjectName
|
||||||
// freeze tag for now
|
|
||||||
if len(templateMeta.ScmSync) > 0 {
|
|
||||||
repository, err := url.Parse(templateMeta.ScmSync)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
common.LogDebug("getting data for ", repository.EscapedPath())
|
|
||||||
split := strings.Split(repository.EscapedPath(), "/")
|
|
||||||
org, repo := split[1], split[2]
|
|
||||||
|
|
||||||
common.LogDebug("getting commit for ", org, " repo ", repo, " fragment ", repository.Fragment)
|
|
||||||
branch, err := gitea.GetCommit(org, repo, repository.Fragment)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set expanded commit url
|
|
||||||
repository.Fragment = branch.SHA
|
|
||||||
templateMeta.ScmSync = repository.String()
|
|
||||||
common.LogDebug("Setting scmsync url to ", templateMeta.ScmSync)
|
|
||||||
}
|
|
||||||
// Cleanup ReleaseTarget and modify affected path entries
|
// Cleanup ReleaseTarget and modify affected path entries
|
||||||
for idx, r := range templateMeta.Repositories {
|
for idx, r := range templateMeta.Repositories {
|
||||||
templateMeta.Repositories[idx].ReleaseTargets = nil
|
templateMeta.Repositories[idx].ReleaseTargets = nil
|
||||||
@@ -1066,7 +1044,6 @@ func main() {
|
|||||||
ObsWebHost = ObsWebHostFromApiHost(*obsApiHost)
|
ObsWebHost = ObsWebHostFromApiHost(*obsApiHost)
|
||||||
}
|
}
|
||||||
|
|
||||||
common.LogDebug("OBS Gitea Host:", GiteaUrl)
|
|
||||||
common.LogDebug("OBS Web Host:", ObsWebHost)
|
common.LogDebug("OBS Web Host:", ObsWebHost)
|
||||||
common.LogDebug("OBS API Host:", *obsApiHost)
|
common.LogDebug("OBS API Host:", *obsApiHost)
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
[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
|
|
||||||
|
|
||||||
9
utils/gitmodules-automerge/README.md
Normal file
9
utils/gitmodules-automerge/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
Purpose
|
||||||
|
-------
|
||||||
|
|
||||||
|
Automatically resolve git configlicts in .gitmodules if there's a conflict
|
||||||
|
due to a merge.
|
||||||
|
|
||||||
|
It uses HEAD and MERGE_HEAD to calculate merge base and pass it to the
|
||||||
|
conflict resolution
|
||||||
|
|
||||||
42
utils/gitmodules-automerge/main.go
Normal file
42
utils/gitmodules-automerge/main.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"src.opensuse.org/autogits/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
common.LogError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gh, err := common.AllocateGitWorkTree(cwd, "", "")
|
||||||
|
if err != nil {
|
||||||
|
common.LogError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
git, err := gh.ReadExistingPath("")
|
||||||
|
if err != nil {
|
||||||
|
common.LogError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
MergeBase := strings.TrimSpace(git.GitExecWithOutputOrPanic("", "merge-base", "HEAD", "MERGE_HEAD"))
|
||||||
|
status, err := git.GitStatus("")
|
||||||
|
if err != nil {
|
||||||
|
common.LogError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range status {
|
||||||
|
if s.Path == ".gitmodules" && s.Status == common.GitStatus_Unmerged {
|
||||||
|
if err := git.GitResolveSubmoduleFileConflict(s, "", MergeBase, "HEAD", "MERGE_HEAD"); err != nil {
|
||||||
|
common.LogError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -39,7 +40,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
AppName = "direct_workflow"
|
AppName = "direct_workflow"
|
||||||
GitAuthor = "AutoGits prjgit-updater"
|
GitAuthor = "AutoGits prjgit-updater"
|
||||||
GitEmail = "autogits-direct@noreply@src.opensuse.org"
|
GitEmail = "adam+autogits-direct@zombino.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configuredRepos map[string][]*common.AutogitConfig
|
var configuredRepos map[string][]*common.AutogitConfig
|
||||||
@@ -52,6 +53,18 @@ func isConfiguredOrg(org *common.Organization) bool {
|
|||||||
return found
|
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{}
|
type RepositoryActionProcessor struct{}
|
||||||
|
|
||||||
func (*RepositoryActionProcessor) ProcessFunc(request *common.Request) error {
|
func (*RepositoryActionProcessor) ProcessFunc(request *common.Request) error {
|
||||||
@@ -59,43 +72,38 @@ func (*RepositoryActionProcessor) ProcessFunc(request *common.Request) error {
|
|||||||
configs, configFound := configuredRepos[action.Organization.Username]
|
configs, configFound := configuredRepos[action.Organization.Username]
|
||||||
|
|
||||||
if !configFound {
|
if !configFound {
|
||||||
common.LogInfo("Repository event for", action.Organization.Username, ". Not configured. Ignoring.", action.Organization.Username)
|
log.Printf("Repository event for %s. Not configured. Ignoring.\n", action.Organization.Username)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
if org, repo, _ := config.GetPrjGit(); org == action.Repository.Owner.Username && repo == action.Repository.Name {
|
if org, repo, _ := config.GetPrjGit(); org == action.Repository.Owner.Username && repo == action.Repository.Name {
|
||||||
common.LogError("+ ignoring repo event for PrjGit repository", config.GitProjectName)
|
log.Println("+ ignoring repo event for PrjGit repository", config.GitProjectName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
processConfiguredRepositoryAction(action, config)
|
err = concatenateErrors(err, processConfiguredRepositoryAction(action, config))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, config *common.AutogitConfig) {
|
func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, config *common.AutogitConfig) error {
|
||||||
gitOrg, gitPrj, gitBranch := config.GetPrjGit()
|
gitOrg, gitPrj, gitBranch := config.GetPrjGit()
|
||||||
git, err := gh.CreateGitHandler(config.Organization)
|
git, err := gh.CreateGitHandler(config.Organization)
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
defer git.Close()
|
defer git.Close()
|
||||||
|
|
||||||
configBranch := config.Branch
|
if len(config.Branch) == 0 {
|
||||||
if len(configBranch) == 0 {
|
config.Branch = action.Repository.Default_Branch
|
||||||
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)
|
prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError("Error accessing/creating prjgit:", gitOrg, gitPrj, gitBranch, err)
|
return fmt.Errorf("Error accessing/creating prjgit: %s/%s#%s err: %w", gitOrg, gitPrj, gitBranch, err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
|
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
|
||||||
@@ -104,29 +112,29 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
|
|||||||
switch action.Action {
|
switch action.Action {
|
||||||
case "created":
|
case "created":
|
||||||
if action.Repository.Object_Format_Name != "sha256" {
|
if action.Repository.Object_Format_Name != "sha256" {
|
||||||
common.LogError(" - '%s' repo is not sha256. Ignoring.", action.Repository.Name)
|
return fmt.Errorf(" - '%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))
|
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name))
|
||||||
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all")
|
defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all")
|
||||||
|
|
||||||
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current"))
|
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current"))
|
||||||
if branch != configBranch {
|
if branch != config.Branch {
|
||||||
if err := git.GitExec(path.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "origin", configBranch+":"+configBranch); err != nil {
|
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", configBranch, ". ignoring as non-existent.", err) // no branch? so ignore repo here
|
return fmt.Errorf("error fetching branch %s. ignoring as non-existent. err: %w", config.Branch, err) // no branch? so ignore repo here
|
||||||
return
|
|
||||||
}
|
}
|
||||||
common.PanicOnError(git.GitExec(path.Join(gitPrj, action.Repository.Name), "checkout", configBranch))
|
common.PanicOnError(git.GitExec(path.Join(gitPrj, action.Repository.Name), "checkout", config.Branch))
|
||||||
}
|
}
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Auto-inclusion "+action.Repository.Name))
|
common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Automatic package inclusion via Direct Workflow"))
|
||||||
if !noop {
|
if !noop {
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "push"))
|
common.PanicOnError(git.GitExec(gitPrj, "push"))
|
||||||
}
|
}
|
||||||
|
|
||||||
case "deleted":
|
case "deleted":
|
||||||
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 || !stat.IsDir() {
|
||||||
common.LogDebug("delete event for", action.Repository.Name, "-- not in project. Ignoring")
|
if DebugMode {
|
||||||
return
|
log.Println("delete event for", action.Repository.Name, "-- not in project. Ignoring")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "rm", action.Repository.Name))
|
common.PanicOnError(git.GitExec(gitPrj, "rm", action.Repository.Name))
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Automatic package removal via Direct Workflow"))
|
common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Automatic package removal via Direct Workflow"))
|
||||||
@@ -135,9 +143,10 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
common.LogError("Unknown action type:", action.Action)
|
return fmt.Errorf("%s: %s", "Unknown action type", action.Action)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type PushActionProcessor struct{}
|
type PushActionProcessor struct{}
|
||||||
@@ -147,44 +156,40 @@ func (*PushActionProcessor) ProcessFunc(request *common.Request) error {
|
|||||||
configs, configFound := configuredRepos[action.Repository.Owner.Username]
|
configs, configFound := configuredRepos[action.Repository.Owner.Username]
|
||||||
|
|
||||||
if !configFound {
|
if !configFound {
|
||||||
common.LogDebug("Repository event for", action.Repository.Owner.Username, ". Not configured. Ignoring.")
|
log.Printf("Repository event for %s. Not configured. Ignoring.\n", action.Repository.Owner.Username)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
if gitOrg, gitPrj, _ := config.GetPrjGit(); gitOrg == action.Repository.Owner.Username && gitPrj == action.Repository.Name {
|
if gitOrg, gitPrj, _ := config.GetPrjGit(); gitOrg == action.Repository.Owner.Username && gitPrj == action.Repository.Name {
|
||||||
common.LogInfo("+ ignoring push to PrjGit repository", config.GitProjectName)
|
log.Println("+ ignoring push to PrjGit repository", config.GitProjectName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
processConfiguredPushAction(action, config)
|
err = concatenateErrors(err, processConfiguredPushAction(action, config))
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func processConfiguredPushAction(action *common.PushWebhookEvent, config *common.AutogitConfig) {
|
func processConfiguredPushAction(action *common.PushWebhookEvent, config *common.AutogitConfig) error {
|
||||||
gitOrg, gitPrj, gitBranch := config.GetPrjGit()
|
gitOrg, gitPrj, gitBranch := config.GetPrjGit()
|
||||||
git, err := gh.CreateGitHandler(config.Organization)
|
git, err := gh.CreateGitHandler(config.Organization)
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
defer git.Close()
|
defer git.Close()
|
||||||
|
|
||||||
common.LogDebug("push to:", action.Repository.Owner.Username, action.Repository.Name, "for:", gitOrg, gitPrj, gitBranch)
|
log.Printf("push to: %s/%s for %s/%s#%s", action.Repository.Owner.Username, action.Repository.Name, gitOrg, gitPrj, gitBranch)
|
||||||
branch := config.Branch
|
if len(config.Branch) == 0 {
|
||||||
if len(branch) == 0 {
|
config.Branch = action.Repository.Default_Branch
|
||||||
if common.IsRemovedBranch(branch) {
|
log.Println(" + default branch", action.Repository.Default_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)
|
prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError("Error accessing/creating prjgit:", gitOrg, gitPrj, err)
|
return fmt.Errorf("Error accessing/creating prjgit: %s/%s err: %w", gitOrg, gitPrj, err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
|
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
|
||||||
@@ -193,25 +198,23 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common
|
|||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
commit, ok := git.GitSubmoduleCommitId(gitPrj, action.Repository.Name, headCommitId)
|
commit, ok := git.GitSubmoduleCommitId(gitPrj, action.Repository.Name, headCommitId)
|
||||||
for ok && action.Head_Commit.Id == commit {
|
for ok && action.Head_Commit.Id == commit {
|
||||||
common.LogDebug(" -- nothing to do, commit already in ProjectGit")
|
log.Println(" -- nothing to do, commit already in ProjectGit")
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil {
|
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil || !stat.IsDir() {
|
||||||
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name)
|
if DebugMode {
|
||||||
common.LogDebug("Pushed to package that is not part of the project. Re-adding...", err)
|
log.Println("Pushed to package that is not part of the project. Ignoring:", err)
|
||||||
} else if !stat.IsDir() {
|
}
|
||||||
common.LogError("Pushed to a package that is not a submodule but exists in the project. Ignoring.")
|
return nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--depth", "1", "--checkout", action.Repository.Name)
|
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--depth", "1", "--checkout", action.Repository.Name)
|
||||||
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all")
|
defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all")
|
||||||
|
|
||||||
if err := git.GitExec(filepath.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "--force", remoteName, branch+":"+branch); err != nil {
|
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:", branch, "Ignoring as non-existent.", err)
|
return fmt.Errorf("error fetching branch %s. ignoring as non-existent. err: %w", config.Branch, err) // no branch? so ignore repo here
|
||||||
return
|
|
||||||
}
|
}
|
||||||
id, err := git.GitRemoteHead(filepath.Join(gitPrj, action.Repository.Name), remoteName, branch)
|
id, err := git.GitRemoteHead(filepath.Join(gitPrj, action.Repository.Name), remoteName, config.Branch)
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
if action.Head_Commit.Id == id {
|
if action.Head_Commit.Id == id {
|
||||||
git.GitExecOrPanic(filepath.Join(gitPrj, action.Repository.Name), "checkout", id)
|
git.GitExecOrPanic(filepath.Join(gitPrj, action.Repository.Name), "checkout", id)
|
||||||
@@ -219,10 +222,11 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common
|
|||||||
if !noop {
|
if !noop {
|
||||||
git.GitExecOrPanic(gitPrj, "push", remoteName)
|
git.GitExecOrPanic(gitPrj, "push", remoteName)
|
||||||
}
|
}
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
common.LogDebug("push of refs not on the configured branch", branch, ". ignoring.")
|
log.Println("push of refs not on the configured branch", config.Branch, ". ignoring.")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyProjectState(git common.Git, org string, config *common.AutogitConfig, configs []*common.AutogitConfig) (err error) {
|
func verifyProjectState(git common.Git, org string, config *common.AutogitConfig, configs []*common.AutogitConfig) (err error) {
|
||||||
@@ -244,61 +248,51 @@ func verifyProjectState(git common.Git, org string, config *common.AutogitConfig
|
|||||||
|
|
||||||
remoteName, err := git.GitClone(gitPrj, gitBranch, repo.SSHURL)
|
remoteName, err := git.GitClone(gitPrj, gitBranch, repo.SSHURL)
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all")
|
defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all")
|
||||||
|
|
||||||
common.LogDebug(" * Getting submodule list")
|
log.Println(" * Getting submodule list")
|
||||||
sub, err := git.GitSubmoduleList(gitPrj, "HEAD")
|
sub, err := git.GitSubmoduleList(gitPrj, "HEAD")
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
|
|
||||||
common.LogDebug(" * Getting package links")
|
log.Println(" * Getting package links")
|
||||||
var pkgLinks []*PackageRebaseLink
|
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 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 {
|
if data, err := os.ReadFile(path.Join(git.GetPath(), gitPrj, common.PrjLinksFile)); err == nil {
|
||||||
pkgLinks, err = parseProjectLinks(data)
|
pkgLinks, err = parseProjectLinks(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError("Cannot parse project links file:", err.Error())
|
log.Println("Cannot parse project links file:", err.Error())
|
||||||
pkgLinks = nil
|
pkgLinks = nil
|
||||||
} else {
|
} else {
|
||||||
ResolveLinks(org, pkgLinks, gitea)
|
ResolveLinks(org, pkgLinks, gitea)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
common.LogInfo(" - No package links defined")
|
log.Println(" - No package links defined")
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check existing submodule that they are updated */
|
/* Check existing submodule that they are updated */
|
||||||
|
|
||||||
isGitUpdated := false
|
isGitUpdated := false
|
||||||
next_package:
|
next_package:
|
||||||
for filename, commitId := range sub {
|
for filename, commitId := range sub {
|
||||||
// ignore project gits
|
// ignore project gits
|
||||||
//for _, c := range configs {
|
//for _, c := range configs {
|
||||||
if gitPrj == filename {
|
if gitPrj == filename {
|
||||||
common.LogDebug(" prjgit as package? ignoring project git:", filename)
|
log.Println(" prjgit as package? ignoring project git:", filename)
|
||||||
continue next_package
|
continue next_package
|
||||||
}
|
}
|
||||||
//}
|
//}
|
||||||
|
|
||||||
branch := config.Branch
|
log.Printf(" verifying package: %s -> %s(%s)", commitId, filename, config.Branch)
|
||||||
common.LogDebug(" verifying package: %s -> %s(%s)", commitId, filename, branch)
|
commits, err := gitea.GetRecentCommits(org, filename, config.Branch, 10)
|
||||||
if repo, err := gitea.GetRepository(org, filename); repo == nil && err == nil {
|
if len(commits) == 0 {
|
||||||
common.LogDebug(" repository removed...")
|
if repo, err := gitea.GetRepository(org, filename); repo == nil && err == nil {
|
||||||
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)
|
git.GitExecOrPanic(gitPrj, "rm", filename)
|
||||||
isGitUpdated = true
|
isGitUpdated = true
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commits, err := gitea.GetRecentCommits(org, filename, branch, 10)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogDebug(" -> failed to fetch recent commits for package:", filename, " Err:", err)
|
log.Println(" -> failed to fetch recent commits for package:", filename, " Err:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +309,7 @@ next_package:
|
|||||||
if l.Pkg == filename {
|
if l.Pkg == filename {
|
||||||
link = l
|
link = l
|
||||||
|
|
||||||
common.LogDebug(" -> linked package")
|
log.Println(" -> linked package")
|
||||||
// so, we need to rebase here. Can't really optimize, so clone entire package tree and remote
|
// so, we need to rebase here. Can't really optimize, so clone entire package tree and remote
|
||||||
pkgPath := path.Join(gitPrj, filename)
|
pkgPath := path.Join(gitPrj, filename)
|
||||||
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--checkout", filename)
|
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--checkout", filename)
|
||||||
@@ -329,7 +323,7 @@ next_package:
|
|||||||
nCommits := len(common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgPath, "rev-list", "^NOW", "HEAD"), "\n"))
|
nCommits := len(common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgPath, "rev-list", "^NOW", "HEAD"), "\n"))
|
||||||
if nCommits > 0 {
|
if nCommits > 0 {
|
||||||
if !noop {
|
if !noop {
|
||||||
git.GitExecOrPanic(pkgPath, "push", "-f", "origin", "HEAD:"+branch)
|
git.GitExecOrPanic(pkgPath, "push", "-f", "origin", "HEAD:"+config.Branch)
|
||||||
}
|
}
|
||||||
isGitUpdated = true
|
isGitUpdated = true
|
||||||
}
|
}
|
||||||
@@ -346,27 +340,42 @@ next_package:
|
|||||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "update", "--init", "--depth", "1", "--checkout", filename))
|
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), "fetch", "--depth", "1", "origin", commits[0].SHA))
|
||||||
common.PanicOnError(git.GitExec(filepath.Join(gitPrj, filename), "checkout", commits[0].SHA))
|
common.PanicOnError(git.GitExec(filepath.Join(gitPrj, filename), "checkout", commits[0].SHA))
|
||||||
common.LogDebug(" -> updated to", commits[0].SHA)
|
log.Println(" -> updated to", commits[0].SHA)
|
||||||
isGitUpdated = true
|
isGitUpdated = true
|
||||||
} else {
|
} else {
|
||||||
// probably need `merge-base` or `rev-list` here instead, or the project updated already
|
// probably need `merge-base` or `rev-list` here instead, or the project updated already
|
||||||
common.LogInfo(" *** Cannot find SHA of last matching update for package:", filename, " Ignoring")
|
log.Println(" *** Cannot find SHA of last matching update for package:", filename, " Ignoring")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// find all missing repositories, and add them
|
// find all missing repositories, and add them
|
||||||
common.LogDebug("checking for missing repositories...")
|
if DebugMode {
|
||||||
|
log.Println("checking for missing repositories...")
|
||||||
|
}
|
||||||
repos, err := gitea.GetOrganizationRepositories(org)
|
repos, err := gitea.GetOrganizationRepositories(org)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
common.LogDebug(" nRepos:", len(repos))
|
if DebugMode {
|
||||||
|
log.Println(" nRepos:", len(repos))
|
||||||
|
}
|
||||||
|
|
||||||
/* Check repositories in org to make sure they are included in project git */
|
/* Check repositories in org to make sure they are included in project git */
|
||||||
next_repo:
|
next_repo:
|
||||||
for _, r := range repos {
|
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 {
|
// for _, c := range configs {
|
||||||
if gitPrj == r.Name {
|
if gitPrj == r.Name {
|
||||||
// ignore project gits
|
// ignore project gits
|
||||||
@@ -381,32 +390,27 @@ next_repo:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
common.LogDebug(" -- checking repository:", r.Name)
|
if DebugMode {
|
||||||
|
log.Println(" -- checking repository:", r.Name)
|
||||||
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 {
|
|
||||||
|
if _, err := gitea.GetRecentCommits(org, r.Name, config.Branch, 1); err != nil {
|
||||||
// assumption that package does not exist, so not part of project
|
// assumption that package does not exist, so not part of project
|
||||||
// https://github.com/go-gitea/gitea/issues/31976
|
// https://github.com/go-gitea/gitea/issues/31976
|
||||||
|
|
||||||
// or, we do not have commits here
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// add repository to git project
|
// add repository to git project
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--depth", "1", r.CloneURL, r.Name))
|
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--depth", "1", r.CloneURL, r.Name))
|
||||||
|
|
||||||
curBranch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
|
if len(config.Branch) > 0 {
|
||||||
if branch != curBranch {
|
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
|
||||||
if err := git.GitExec(path.Join(gitPrj, r.Name), "fetch", "--depth", "1", "origin", branch+":"+branch); err != nil {
|
if branch != config.Branch {
|
||||||
return fmt.Errorf("Fetching branch %s for %s/%s failed. Ignoring.", branch, repo.Owner.UserName, r.Name)
|
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))
|
||||||
}
|
}
|
||||||
common.PanicOnError(git.GitExec(path.Join(gitPrj, r.Name), "checkout", branch))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isGitUpdated = true
|
isGitUpdated = true
|
||||||
@@ -419,7 +423,10 @@ next_repo:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
common.LogInfo("Verification finished for ", org, ", prjgit:", config.GitProjectName)
|
if DebugMode {
|
||||||
|
log.Println("Verification finished for ", org, ", prjgit:", config.GitProjectName)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,17 +437,17 @@ var checkInterval time.Duration
|
|||||||
func checkOrg(org string, configs []*common.AutogitConfig) {
|
func checkOrg(org string, configs []*common.AutogitConfig) {
|
||||||
git, err := gh.CreateGitHandler(org)
|
git, err := gh.CreateGitHandler(org)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError("Failed to allocate GitHandler:", err)
|
log.Println("Faield to allocate GitHandler:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer git.Close()
|
defer git.Close()
|
||||||
|
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
common.LogInfo(" ++ starting verification, org:", org, "config:", config.GitProjectName)
|
log.Printf(" ++ starting verification, org: `%s` config: `%s`\n", org, config.GitProjectName)
|
||||||
if err := verifyProjectState(git, org, config, configs); err != nil {
|
if err := verifyProjectState(git, org, config, configs); err != nil {
|
||||||
common.LogError(" *** verification failed, org:", org, err)
|
log.Printf(" *** verification failed, org: `%s`, err: %#v\n", org, err)
|
||||||
} else {
|
} else {
|
||||||
common.LogError(" ++ verification complete, org:", org, config.GitProjectName)
|
log.Printf(" ++ verification complete, org: `%s` config: `%s`\n", org, config.GitProjectName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -449,7 +456,7 @@ func checkRepos() {
|
|||||||
for org, configs := range configuredRepos {
|
for org, configs := range configuredRepos {
|
||||||
if checkInterval > 0 {
|
if checkInterval > 0 {
|
||||||
sleepInterval := checkInterval - checkInterval/2 + time.Duration(rand.Int63n(int64(checkInterval)))
|
sleepInterval := checkInterval - checkInterval/2 + time.Duration(rand.Int63n(int64(checkInterval)))
|
||||||
common.LogInfo(" - sleep interval", sleepInterval, "until next check")
|
log.Println(" - sleep interval", sleepInterval, "until next check")
|
||||||
time.Sleep(sleepInterval)
|
time.Sleep(sleepInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,9 +468,9 @@ func consistencyCheckProcess() {
|
|||||||
if checkOnStart {
|
if checkOnStart {
|
||||||
savedCheckInterval := checkInterval
|
savedCheckInterval := checkInterval
|
||||||
checkInterval = 0
|
checkInterval = 0
|
||||||
common.LogInfo("== Startup consistency check begin...")
|
log.Println("== Startup consistency check begin...")
|
||||||
checkRepos()
|
checkRepos()
|
||||||
common.LogInfo("== Startup consistency check done...")
|
log.Println("== Startup consistency check done...")
|
||||||
checkInterval = savedCheckInterval
|
checkInterval = savedCheckInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,8 +485,7 @@ var gh common.GitHandlerGenerator
|
|||||||
func updateConfiguration(configFilename string, orgs *[]string) {
|
func updateConfiguration(configFilename string, orgs *[]string) {
|
||||||
configFile, err := common.ReadConfigFile(configFilename)
|
configFile, err := common.ReadConfigFile(configFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError(err)
|
log.Fatal(err)
|
||||||
os.Exit(4)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configs, _ := common.ResolveWorkflowConfigs(gitea, configFile)
|
configs, _ := common.ResolveWorkflowConfigs(gitea, configFile)
|
||||||
@@ -487,7 +493,9 @@ func updateConfiguration(configFilename string, orgs *[]string) {
|
|||||||
*orgs = make([]string, 0, 1)
|
*orgs = make([]string, 0, 1)
|
||||||
for _, c := range configs {
|
for _, c := range configs {
|
||||||
if slices.Contains(c.Workflows, "direct") {
|
if slices.Contains(c.Workflows, "direct") {
|
||||||
common.LogDebug(" + adding org:", c.Organization, ", branch:", c.Branch, ", prjgit:", c.GitProjectName)
|
if DebugMode {
|
||||||
|
log.Printf(" + adding org: '%s', branch: '%s', prjgit: '%s'\n", c.Organization, c.Branch, c.GitProjectName)
|
||||||
|
}
|
||||||
configs := configuredRepos[c.Organization]
|
configs := configuredRepos[c.Organization]
|
||||||
if configs == nil {
|
if configs == nil {
|
||||||
configs = make([]*common.AutogitConfig, 0, 1)
|
configs = make([]*common.AutogitConfig, 0, 1)
|
||||||
@@ -512,12 +520,10 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if err := common.RequireGiteaSecretToken(); err != nil {
|
if err := common.RequireGiteaSecretToken(); err != nil {
|
||||||
common.LogError(err)
|
log.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
if err := common.RequireRabbitSecrets(); err != nil {
|
if err := common.RequireRabbitSecrets(); err != nil {
|
||||||
common.LogError(err)
|
log.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defs := &common.RabbitMQGiteaEventsProcessor{}
|
defs := &common.RabbitMQGiteaEventsProcessor{}
|
||||||
@@ -526,14 +532,12 @@ func main() {
|
|||||||
if len(*basePath) == 0 {
|
if len(*basePath) == 0 {
|
||||||
*basePath, err = os.MkdirTemp(os.TempDir(), AppName)
|
*basePath, err = os.MkdirTemp(os.TempDir(), AppName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError(err)
|
log.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gh, err = common.AllocateGitWorkTree(*basePath, GitAuthor, GitEmail)
|
gh, err = common.AllocateGitWorkTree(*basePath, GitAuthor, GitEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError(err)
|
log.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle reconfiguration
|
// handle reconfiguration
|
||||||
@@ -548,10 +552,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sig != syscall.SIGHUP {
|
if sig != syscall.SIGHUP {
|
||||||
common.LogError("Unexpected signal received:", sig)
|
log.Println("Unexpected signal received:", sig)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
common.LogError("*** Reconfiguring ***")
|
log.Println("*** Reconfiguring ***")
|
||||||
updateConfiguration(*configFilename, &defs.Orgs)
|
updateConfiguration(*configFilename, &defs.Orgs)
|
||||||
defs.Connection().UpdateTopics(defs)
|
defs.Connection().UpdateTopics(defs)
|
||||||
}
|
}
|
||||||
@@ -563,25 +567,23 @@ func main() {
|
|||||||
gitea = common.AllocateGiteaTransport(*giteaUrl)
|
gitea = common.AllocateGiteaTransport(*giteaUrl)
|
||||||
CurrentUser, err := gitea.GetCurrentUser()
|
CurrentUser, err := gitea.GetCurrentUser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError("Cannot fetch current user:", err)
|
log.Fatalln("Cannot fetch current user:", err)
|
||||||
os.Exit(2)
|
|
||||||
}
|
}
|
||||||
common.LogInfo("Current User:", CurrentUser.UserName)
|
log.Println("Current User:", CurrentUser.UserName)
|
||||||
|
|
||||||
updateConfiguration(*configFilename, &defs.Orgs)
|
updateConfiguration(*configFilename, &defs.Orgs)
|
||||||
|
|
||||||
defs.Connection().RabbitURL, err = url.Parse(*rabbitUrl)
|
defs.Connection().RabbitURL, err = url.Parse(*rabbitUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError("cannot parse server URL. Err:", err)
|
log.Panicf("cannot parse server URL. Err: %#v\n", err)
|
||||||
os.Exit(3)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go consistencyCheckProcess()
|
go consistencyCheckProcess()
|
||||||
common.LogInfo("defs:", *defs)
|
log.Println("defs:", *defs)
|
||||||
|
|
||||||
defs.Handlers = make(map[string]common.RequestProcessor)
|
defs.Handlers = make(map[string]common.RequestProcessor)
|
||||||
defs.Handlers[common.RequestType_Push] = &PushActionProcessor{}
|
defs.Handlers[common.RequestType_Push] = &PushActionProcessor{}
|
||||||
defs.Handlers[common.RequestType_Repository] = &RepositoryActionProcessor{}
|
defs.Handlers[common.RequestType_Repository] = &RepositoryActionProcessor{}
|
||||||
|
|
||||||
common.LogError(common.ProcessRabbitMQEvents(defs))
|
log.Fatal(common.ProcessRabbitMQEvents(defs))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,7 +269,6 @@ func (pr *PRProcessor) RebaseAndSkipSubmoduleCommits(prset *common.PRSet, branch
|
|||||||
}
|
}
|
||||||
|
|
||||||
var updatePrjGitError_requeue error = errors.New("Commits do not match. Requeing after 5 seconds.")
|
var updatePrjGitError_requeue error = errors.New("Commits do not match. Requeing after 5 seconds.")
|
||||||
|
|
||||||
func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||||
_, _, PrjGitBranch := prset.Config.GetPrjGit()
|
_, _, PrjGitBranch := prset.Config.GetPrjGit()
|
||||||
PrjGitPR, err := prset.GetPrjGitPR()
|
PrjGitPR, err := prset.GetPrjGitPR()
|
||||||
@@ -573,7 +572,7 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
|||||||
|
|
||||||
type RequestProcessor struct {
|
type RequestProcessor struct {
|
||||||
configuredRepos map[string][]*common.AutogitConfig
|
configuredRepos map[string][]*common.AutogitConfig
|
||||||
recursive int
|
recursive int
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcesPullRequest(pr *models.PullRequest, configs []*common.AutogitConfig) error {
|
func ProcesPullRequest(pr *models.PullRequest, configs []*common.AutogitConfig) error {
|
||||||
@@ -614,7 +613,7 @@ func (w *RequestProcessor) ProcessFunc(request *common.Request) (err error) {
|
|||||||
common.LogError("Cannot find PR for issue:", req.Pull_Request.Base.Repo.Owner.Username, req.Pull_Request.Base.Repo.Name, req.Pull_Request.Number)
|
common.LogError("Cannot find PR for issue:", req.Pull_Request.Base.Repo.Owner.Username, req.Pull_Request.Base.Repo.Name, req.Pull_Request.Number)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if req, ok := request.Data.(*common.IssueCommentWebhookEvent); ok {
|
} else if req, ok := request.Data.(*common.IssueWebhookEvent); ok {
|
||||||
pr, err = Gitea.GetPullRequest(req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
|
pr, err = Gitea.GetPullRequest(req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError("Cannot find PR for issue:", req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
|
common.LogError("Cannot find PR for issue:", req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
|
||||||
|
|||||||
Reference in New Issue
Block a user