Compare commits
2 Commits
submodulem
...
mergemodes
| Author | SHA256 | Date | |
|---|---|---|---|
| db70452cbc | |||
| 53eebb75f7 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,5 +5,3 @@
|
|||||||
/integration/rabbitmq-data
|
/integration/rabbitmq-data
|
||||||
/integration/workflow-pr-repos
|
/integration/workflow-pr-repos
|
||||||
__pycache__/
|
__pycache__/
|
||||||
utils/gitmodules-automerge/gitmodules-automerge
|
|
||||||
utils/hujson/hujson
|
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ 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
|
||||||
@@ -133,9 +132,6 @@ go build \
|
|||||||
go build \
|
go build \
|
||||||
-C utils/maintainer-update \
|
-C utils/maintainer-update \
|
||||||
-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
|
||||||
@@ -185,13 +181,10 @@ install -D -m0755 obs-status-service/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 -m0644 systemd/workflow-direct@.service %{buildroot}%{_unitdir}/workflow-direct@.service
|
||||||
install -D -m0644 systemd/workflow-direct.target %{buildroot}%{_unitdir}/workflow-direct.target
|
|
||||||
install -D -m0755 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr
|
install -D -m0755 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr
|
||||||
install -D -m0644 systemd/workflow-pr@.service %{buildroot}%{_unitdir}/workflow-pr@.service
|
install -D -m0644 systemd/workflow-pr@.service %{buildroot}%{_unitdir}/workflow-pr@.service
|
||||||
install -D -m0644 systemd/workflow-pr.target %{buildroot}%{_unitdir}/workflow-pr.target
|
|
||||||
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
|
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
|
||||||
install -D -m0755 utils/maintainer-update/maintainer-update %{buildroot}%{_bindir}/maintainer-update
|
install -D -m0755 utils/maintainer-update/maintainer-update %{buildroot}%{_bindir}/maintainer-update
|
||||||
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
|
||||||
@@ -242,28 +235,28 @@ install -D -m0755 utils/gitmodules-automerge/gitmodules-automerge
|
|||||||
%service_del_postun obs-status-service.service
|
%service_del_postun obs-status-service.service
|
||||||
|
|
||||||
%pre workflow-direct
|
%pre workflow-direct
|
||||||
%service_add_pre workflow-direct.target
|
%service_add_pre workflow-direct.service
|
||||||
|
|
||||||
%post workflow-direct
|
%post workflow-direct
|
||||||
%service_add_post workflow-direct.target
|
%service_add_post workflow-direct.service
|
||||||
|
|
||||||
%preun workflow-direct
|
%preun workflow-direct
|
||||||
%service_del_preun workflow-direct.target
|
%service_del_preun workflow-direct.service
|
||||||
|
|
||||||
%postun workflow-direct
|
%postun workflow-direct
|
||||||
%service_del_postun workflow-direct.target
|
%service_del_postun workflow-direct.service
|
||||||
|
|
||||||
%pre workflow-pr
|
%pre workflow-pr
|
||||||
%service_add_pre workflow-pr.target
|
%service_add_pre workflow-pr.service
|
||||||
|
|
||||||
%post workflow-pr
|
%post workflow-pr
|
||||||
%service_add_post workflow-pr.target
|
%service_add_post workflow-pr.service
|
||||||
|
|
||||||
%preun workflow-pr
|
%preun workflow-pr
|
||||||
%service_del_preun workflow-pr.target
|
%service_del_preun workflow-pr.service
|
||||||
|
|
||||||
%postun workflow-pr
|
%postun workflow-pr
|
||||||
%service_del_postun workflow-pr.target
|
%service_del_postun workflow-pr.service
|
||||||
|
|
||||||
%files devel-importer
|
%files devel-importer
|
||||||
%license COPYING
|
%license COPYING
|
||||||
@@ -311,19 +304,16 @@ install -D -m0755 utils/gitmodules-automerge/gitmodules-automerge
|
|||||||
%license COPYING
|
%license COPYING
|
||||||
%{_bindir}/hujson
|
%{_bindir}/hujson
|
||||||
%{_bindir}/maintainer-update
|
%{_bindir}/maintainer-update
|
||||||
%{_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
|
%{_unitdir}/workflow-direct@.service
|
||||||
%{_unitdir}/workflow-direct.target
|
|
||||||
|
|
||||||
%files workflow-pr
|
%files workflow-pr
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%doc workflow-pr/README.md
|
%doc workflow-pr/README.md
|
||||||
%{_bindir}/workflow-pr
|
%{_bindir}/workflow-pr
|
||||||
%{_unitdir}/workflow-pr@.service
|
%{_unitdir}/workflow-pr@.service
|
||||||
%{_unitdir}/workflow-pr.target
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ const (
|
|||||||
|
|
||||||
Permission_ForceMerge = "force-merge"
|
Permission_ForceMerge = "force-merge"
|
||||||
Permission_Group = "release-engineering"
|
Permission_Group = "release-engineering"
|
||||||
|
|
||||||
|
MergeModeFF = "ff-only"
|
||||||
|
MergeModeReplace = "replace"
|
||||||
|
MergeModeDevel = "devel"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigFile struct {
|
type ConfigFile struct {
|
||||||
@@ -52,9 +56,9 @@ type ReviewGroup struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type QAConfig struct {
|
type QAConfig struct {
|
||||||
Name string
|
Name string
|
||||||
Origin string
|
Origin string
|
||||||
Label string // requires this gitea lable to be set or skipped
|
Label string // requires this gitea lable to be set or skipped
|
||||||
BuildDisableRepos []string // which repos to build disable in the new project
|
BuildDisableRepos []string // which repos to build disable in the new project
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +92,8 @@ type AutogitConfig struct {
|
|||||||
Committers []string // group in addition to Reviewers and Maintainers that can order the bot around, mostly as helper for factory-maintainers
|
Committers []string // group in addition to Reviewers and Maintainers that can order the bot around, mostly as helper for factory-maintainers
|
||||||
Subdirs []string // list of directories to sort submodules into. Needed b/c _manifest cannot list non-existent directories
|
Subdirs []string // list of directories to sort submodules into. Needed b/c _manifest cannot list non-existent directories
|
||||||
|
|
||||||
Labels map[string]string // list of tags, if not default, to apply
|
Labels map[string]string // list of tags, if not default, to apply
|
||||||
|
MergeMode string // project merge mode
|
||||||
|
|
||||||
NoProjectGitPR bool // do not automatically create project git PRs, just assign reviewers and assume somethign else creates the ProjectGit PR
|
NoProjectGitPR bool // do not automatically create project git PRs, just assign reviewers and assume somethign else creates the ProjectGit PR
|
||||||
ManualMergeOnly bool // only merge with "Merge OK" comment by Project Maintainers and/or Package Maintainers and/or reviewers
|
ManualMergeOnly bool // only merge with "Merge OK" comment by Project Maintainers and/or Package Maintainers and/or reviewers
|
||||||
@@ -183,6 +188,17 @@ func ReadWorkflowConfig(gitea GiteaFileContentAndRepoFetcher, git_project string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.GitProjectName = config.GitProjectName + "#" + branch
|
config.GitProjectName = config.GitProjectName + "#" + branch
|
||||||
|
|
||||||
|
// verify merge modes
|
||||||
|
switch config.MergeMode {
|
||||||
|
case MergeModeFF, MergeModeDevel, MergeModeReplace:
|
||||||
|
break // good results
|
||||||
|
case "":
|
||||||
|
config.MergeMode = MergeModeFF
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unsupported merge mode in %s: %s", git_project, config.MergeMode)
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -342,3 +342,67 @@ func TestConfigPermissions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigMergeModeParser(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
json string
|
||||||
|
mergeMode string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
json: "{}",
|
||||||
|
mergeMode: common.MergeModeFF,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ff-only",
|
||||||
|
json: `{"MergeMode": "ff-only"}`,
|
||||||
|
mergeMode: common.MergeModeFF,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replace",
|
||||||
|
json: `{"MergeMode": "replace"}`,
|
||||||
|
mergeMode: common.MergeModeReplace,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "devel",
|
||||||
|
json: `{"MergeMode": "devel"}`,
|
||||||
|
mergeMode: common.MergeModeDevel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported",
|
||||||
|
json: `{"MergeMode": "invalid"}`,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
repo := models.Repository{
|
||||||
|
DefaultBranch: "master",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctl := gomock.NewController(t)
|
||||||
|
gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl)
|
||||||
|
gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.json), "abc", nil)
|
||||||
|
gitea.EXPECT().GetRepository("foo", "bar").Return(&repo, nil)
|
||||||
|
|
||||||
|
config, err := common.ReadWorkflowConfig(gitea, "foo/bar")
|
||||||
|
if test.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error, got nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.MergeMode != test.mergeMode {
|
||||||
|
t.Errorf("Expected MergeMode %s, got %s", test.mergeMode, config.MergeMode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,296 +0,0 @@
|
|||||||
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,7 +19,9 @@ package common
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -42,11 +44,6 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -78,7 +75,6 @@ type Git interface {
|
|||||||
GitExecQuietOrPanic(cwd string, params ...string)
|
GitExecQuietOrPanic(cwd string, params ...string)
|
||||||
|
|
||||||
GitDiffLister
|
GitDiffLister
|
||||||
GitSubmoduleFileConflictResolver
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GitHandlerImpl struct {
|
type GitHandlerImpl struct {
|
||||||
@@ -361,20 +357,13 @@ 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" + identityFile,
|
"GIT_SSH_COMMAND=/usr/bin/ssh -o StrictHostKeyChecking=yes" + identityFile,
|
||||||
}
|
}
|
||||||
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...)
|
||||||
}
|
}
|
||||||
@@ -1017,10 +1006,193 @@ func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string)
|
|||||||
return subCommitId, len(subCommitId) > 0
|
return subCommitId, len(subCommitId) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *GitHandlerImpl) GitExecWithDataParse(cwd string, dataprocessor func(io.ByteReader) (Data, error), gitcmd string, args ...string) (Data, error) {
|
const (
|
||||||
LogDebug("getting", gitcmd)
|
GitStatus_Untracked = 0
|
||||||
args = append([]string{gitcmd}, args...)
|
GitStatus_Modified = 1
|
||||||
cmd := exec.Command("/usr/bin/git", args...)
|
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 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",
|
||||||
@@ -1037,12 +1209,7 @@ func (e *GitHandlerImpl) GitExecWithDataParse(cwd string, dataprocessor func(io.
|
|||||||
LogError("Error running command", cmd.Args, err)
|
LogError("Error running command", cmd.Args, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataprocessor(bytes.NewReader(out))
|
return parseGitStatusData(bufio.NewReader(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) {
|
||||||
@@ -1067,122 +1234,3 @@ 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,7 +24,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"runtime/debug"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -94,145 +93,6 @@ 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"
|
||||||
@@ -724,15 +584,12 @@ func TestGitStatusParse(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if len(r) != len(test.res) {
|
||||||
res := r.([]GitStatusData)
|
t.Fatal("len(r):", len(r), "is not expected", len(test.res))
|
||||||
|
|
||||||
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(res, expected) {
|
if !slices.Contains(r, expected) {
|
||||||
t.Fatal("result", r, "doesn't contains expected", expected)
|
t.Fatal("result", r, "doesn't contains expected", expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
232
common/pr.go
232
common/pr.go
@@ -4,6 +4,8 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -550,6 +552,145 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
|||||||
return is_manually_reviewed_ok
|
return is_manually_reviewed_ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rs *PRSet) AddMergeCommit(git Git, remote string, pr int) bool {
|
||||||
|
prinfo := rs.PRs[pr]
|
||||||
|
|
||||||
|
LogDebug("Adding merge commit for %s", PRtoString(prinfo.PR))
|
||||||
|
if !prinfo.PR.AllowMaintainerEdit {
|
||||||
|
LogError(" PR is not editable by maintainer")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := prinfo.PR.Base.Repo
|
||||||
|
head := prinfo.PR.Head
|
||||||
|
br := rs.Config.Branch
|
||||||
|
if len(br) == 0 {
|
||||||
|
br = prinfo.PR.Base.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("Merge branch '%s' into %s", br, head.Name)
|
||||||
|
if err := git.GitExec(repo.Name, "merge", "--no-ff", "--no-commit", "-X", "theirs", head.Sha); err != nil {
|
||||||
|
if err := git.GitExec(repo.Name, "merge", "--no-ff", "--no-commit", "--allow-unrelated-histories", "-X", "theirs", head.Sha); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
LogError("WARNING: Merging unrelated histories")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure only files that are in head.Sha are kept
|
||||||
|
git.GitExecOrPanic(repo.Name, "read-tree", "-m", head.Sha)
|
||||||
|
git.GitExecOrPanic(repo.Name, "commit", "-m", msg)
|
||||||
|
git.GitExecOrPanic(repo.Name, "clean", "-fxd")
|
||||||
|
|
||||||
|
if !IsDryRun {
|
||||||
|
git.GitExecOrPanic(repo.Name, "push", remote, "HEAD:"+head.Name)
|
||||||
|
prinfo.PR.Head.Sha = strings.TrimSpace(git.GitExecWithOutputOrPanic(repo.Name, "rev-list", "-1", "HEAD")) // need to update as it's pushed but pr not refetched
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *PRSet) HasMerge(git Git, pr int) bool {
|
||||||
|
prinfo := rs.PRs[pr]
|
||||||
|
|
||||||
|
repo := prinfo.PR.Base.Repo
|
||||||
|
head := prinfo.PR.Head
|
||||||
|
br := rs.Config.Branch
|
||||||
|
if len(br) == 0 {
|
||||||
|
br = prinfo.PR.Base.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
parents, err := git.GitExecWithOutput(repo.Name, "show", "-s", "--format=%P", head.Sha)
|
||||||
|
if err == nil {
|
||||||
|
p := strings.Fields(strings.TrimSpace(parents))
|
||||||
|
if len(p) == 2 {
|
||||||
|
targetHead, _ := git.GitExecWithOutput(repo.Name, "rev-parse", "HEAD")
|
||||||
|
targetHead = strings.TrimSpace(targetHead)
|
||||||
|
if p[0] == targetHead || p[1] == targetHead {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *PRSet) PrepareForMerge(git Git) bool {
|
||||||
|
// verify that package can merge here. Checkout current target branch of each PRSet, make a temporary branch
|
||||||
|
// PR_#_mergetest and perform the merge based
|
||||||
|
|
||||||
|
if rs.Config.MergeMode == MergeModeDevel {
|
||||||
|
return true // always can merge as we set branch here, not merge anything
|
||||||
|
} else {
|
||||||
|
// make sure that all the package PRs are in mergeable state
|
||||||
|
for idx, prinfo := range rs.PRs {
|
||||||
|
if rs.IsPrjGitPR(prinfo.PR) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := prinfo.PR.Base.Repo
|
||||||
|
head := prinfo.PR.Head
|
||||||
|
br := rs.Config.Branch
|
||||||
|
if len(br) == 0 {
|
||||||
|
br = prinfo.PR.Base.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
remote, err := git.GitClone(repo.Name, br, repo.SSHURL)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
git.GitExecOrPanic(repo.Name, "fetch", remote, head.Sha)
|
||||||
|
switch rs.Config.MergeMode {
|
||||||
|
case MergeModeFF:
|
||||||
|
if err := git.GitExec(repo.Name, "merge-base", "--is-ancestor", "HEAD", head.Sha); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case MergeModeReplace:
|
||||||
|
Verify:
|
||||||
|
if err := git.GitExec(repo.Name, "merge-base", "--is-ancestor", "HEAD", head.Sha); err != nil {
|
||||||
|
if !rs.HasMerge(git, idx) {
|
||||||
|
forkRemote, err := git.GitClone(repo.Name, head.Name, head.Repo.SSHURL)
|
||||||
|
if err != nil {
|
||||||
|
LogError("Failed to clone head repo:", head.Name, head.Repo.SSHURL)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
LogDebug("Merge commit is missing and this is not FF merge possibility")
|
||||||
|
git.GitExecOrPanic(repo.Name, "checkout", remote+"/"+br)
|
||||||
|
if !rs.AddMergeCommit(git, forkRemote, idx) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !IsDryRun {
|
||||||
|
goto Verify
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we check project git if mergeable
|
||||||
|
prjgit_info, err := rs.GetPrjGitPR()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
prjgit := prjgit_info.PR
|
||||||
|
|
||||||
|
_, _, prjgitBranch := rs.Config.GetPrjGit()
|
||||||
|
remote, err := git.GitClone(DefaultGitPrj, prjgitBranch, prjgit.Base.Repo.SSHURL)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
testBranch := fmt.Sprintf("PR_%d_mergetest", prjgit.Index)
|
||||||
|
git.GitExecOrPanic(DefaultGitPrj, "fetch", remote, prjgit.Head.Sha)
|
||||||
|
if err := git.GitExec(DefaultGitPrj, "checkout", "-B", testBranch, prjgit.Base.Sha); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := git.GitExec(DefaultGitPrj, "merge", "--no-ff", "--no-commit", prjgit.Head.Sha); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
||||||
prjgit_info, err := rs.GetPrjGitPR()
|
prjgit_info, err := rs.GetPrjGitPR()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -579,8 +720,80 @@ 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 {
|
||||||
if resolveError := git.GitResolveConflicts(DefaultGitPrj, prjgit.MergeBase, prjgit.Base.Sha, prjgit.Head.Sha); resolveError != nil {
|
status, statusErr := git.GitStatus(DefaultGitPrj)
|
||||||
return fmt.Errorf("Merge failed. (%w): %w", err, resolveError)
|
if statusErr != nil {
|
||||||
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,8 +829,12 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
|||||||
}
|
}
|
||||||
prinfo.RemoteName, err = git.GitClone(repo.Name, br, repo.SSHURL)
|
prinfo.RemoteName, err = git.GitClone(repo.Name, br, repo.SSHURL)
|
||||||
PanicOnError(err)
|
PanicOnError(err)
|
||||||
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
|
if rs.Config.MergeMode == MergeModeDevel {
|
||||||
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
|
git.GitExecOrPanic(repo.Name, "checkout", "-B", br, head.Sha)
|
||||||
|
} else {
|
||||||
|
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
|
||||||
|
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,7 +851,12 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
|||||||
repo := prinfo.PR.Base.Repo
|
repo := prinfo.PR.Base.Repo
|
||||||
|
|
||||||
if !IsDryRun {
|
if !IsDryRun {
|
||||||
git.GitExecOrPanic(repo.Name, "push", prinfo.RemoteName)
|
params := []string{"push"}
|
||||||
|
if rs.Config.MergeMode == MergeModeDevel {
|
||||||
|
params = append(params, "-f")
|
||||||
|
}
|
||||||
|
params = append(params, prinfo.RemoteName)
|
||||||
|
git.GitExecOrPanic(repo.Name, params...)
|
||||||
} else {
|
} else {
|
||||||
LogInfo("*** WOULD push", repo.Name, "to", prinfo.RemoteName)
|
LogInfo("*** WOULD push", repo.Name, "to", prinfo.RemoteName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package common_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@@ -1223,13 +1224,12 @@ 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{
|
||||||
UserName: "org",
|
UserName: "org",
|
||||||
},
|
},
|
||||||
SSHURL: "file://" + path.Join(repoDir, "prjgit"),
|
SSHURL: "ssh://git@src.opensuse.org/org/prj.git",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Head: &models.PRBranchInfo{
|
Head: &models.PRBranchInfo{
|
||||||
@@ -1244,13 +1244,12 @@ 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{
|
||||||
UserName: "org",
|
UserName: "org",
|
||||||
},
|
},
|
||||||
SSHURL: "file://" + path.Join(cmd.Dir, "prjgit"),
|
SSHURL: "ssh://git@src.opensuse.org/org/prj.git",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Head: &models.PRBranchInfo{
|
Head: &models.PRBranchInfo{
|
||||||
@@ -1264,8 +1263,8 @@ 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.NewMockGiteaPRTimelineReviewFetcher(ctl)
|
mock := mock_common.NewMockGiteaPRTimelineReviewFetcher(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)
|
||||||
|
|
||||||
testDir := t.TempDir()
|
testDir := t.TempDir()
|
||||||
@@ -1340,3 +1339,346 @@ func TestPRChanges(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPRPrepareForMerge(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setup func(*mock_common.MockGit, *models.PullRequest, *models.PullRequest)
|
||||||
|
config *common.AutogitConfig
|
||||||
|
expected bool
|
||||||
|
editable bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Success Devel",
|
||||||
|
config: &common.AutogitConfig{
|
||||||
|
Organization: "org",
|
||||||
|
GitProjectName: "org/_ObsPrj#master",
|
||||||
|
MergeMode: common.MergeModeDevel,
|
||||||
|
},
|
||||||
|
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Success FF",
|
||||||
|
config: &common.AutogitConfig{
|
||||||
|
Organization: "org",
|
||||||
|
GitProjectName: "org/_ObsPrj#master",
|
||||||
|
MergeMode: common.MergeModeFF,
|
||||||
|
},
|
||||||
|
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
||||||
|
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(nil)
|
||||||
|
|
||||||
|
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil)
|
||||||
|
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(nil)
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Success Replace MergeCommit",
|
||||||
|
config: &common.AutogitConfig{
|
||||||
|
Organization: "org",
|
||||||
|
GitProjectName: "org/_ObsPrj#master",
|
||||||
|
MergeMode: common.MergeModeReplace,
|
||||||
|
},
|
||||||
|
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
||||||
|
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha)
|
||||||
|
// merge-base fails initially
|
||||||
|
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(fmt.Errorf("not ancestor"))
|
||||||
|
// HasMerge returns true
|
||||||
|
m.EXPECT().GitExecWithOutput("pkg", "show", "-s", "--format=%P", pkgPR.Head.Sha).Return("parent1 target_head", nil)
|
||||||
|
m.EXPECT().GitExecWithOutput("pkg", "rev-parse", "HEAD").Return("target_head", nil)
|
||||||
|
|
||||||
|
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil)
|
||||||
|
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(nil)
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Merge Conflict in PrjGit",
|
||||||
|
config: &common.AutogitConfig{
|
||||||
|
Organization: "org",
|
||||||
|
GitProjectName: "org/_ObsPrj#master",
|
||||||
|
MergeMode: common.MergeModeFF,
|
||||||
|
},
|
||||||
|
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
||||||
|
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(nil)
|
||||||
|
|
||||||
|
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil)
|
||||||
|
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(fmt.Errorf("conflict"))
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Not FF in PkgGit",
|
||||||
|
config: &common.AutogitConfig{
|
||||||
|
Organization: "org",
|
||||||
|
GitProjectName: "org/_ObsPrj#master",
|
||||||
|
MergeMode: common.MergeModeFF,
|
||||||
|
},
|
||||||
|
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
||||||
|
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(fmt.Errorf("not ancestor"))
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Success Replace with AddMergeCommit",
|
||||||
|
config: &common.AutogitConfig{
|
||||||
|
Organization: "org",
|
||||||
|
GitProjectName: "org/_ObsPrj#master",
|
||||||
|
MergeMode: common.MergeModeReplace,
|
||||||
|
},
|
||||||
|
editable: true,
|
||||||
|
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
||||||
|
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha)
|
||||||
|
// First merge-base fails
|
||||||
|
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(fmt.Errorf("not ancestor"))
|
||||||
|
// HasMerge returns false
|
||||||
|
m.EXPECT().GitExecWithOutput("pkg", "show", "-s", "--format=%P", pkgPR.Head.Sha).Return("parent1", nil)
|
||||||
|
m.EXPECT().GitClone("pkg", pkgPR.Head.Name, pkgPR.Base.Repo.SSHURL).Return("origin_fork", nil)
|
||||||
|
// AddMergeCommit is called
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "checkout", "origin/master")
|
||||||
|
m.EXPECT().GitExec("pkg", "merge", "--no-ff", "--no-commit", "-X", "theirs", pkgPR.Head.Sha).Return(nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "read-tree", "-m", pkgPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "commit", "-m", gomock.Any())
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "clean", "-fxd")
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "push", "origin_fork", "HEAD:"+pkgPR.Head.Name)
|
||||||
|
m.EXPECT().GitExecWithOutputOrPanic("pkg", "rev-list", "-1", "HEAD").Return("new_pkg_head_sha")
|
||||||
|
// Second merge-base succeeds (after goto Verify)
|
||||||
|
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", "new_pkg_head_sha").Return(nil)
|
||||||
|
|
||||||
|
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil)
|
||||||
|
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(nil)
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
prjPR := &models.PullRequest{
|
||||||
|
Index: 1,
|
||||||
|
Base: &models.PRBranchInfo{
|
||||||
|
Name: "master",
|
||||||
|
Sha: "base_sha",
|
||||||
|
Repo: &models.Repository{
|
||||||
|
Owner: &models.User{UserName: "org"},
|
||||||
|
Name: "_ObsPrj",
|
||||||
|
SSHURL: "ssh://git@src.opensuse.org/org/_ObsPrj.git",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Head: &models.PRBranchInfo{
|
||||||
|
Sha: "head_sha",
|
||||||
|
Repo: &models.Repository{
|
||||||
|
Owner: &models.User{UserName: "org"},
|
||||||
|
Name: "_ObsPrj",
|
||||||
|
SSHURL: "ssh://git@src.opensuse.org/org/_ObsPrj.git",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgPR := &models.PullRequest{
|
||||||
|
Index: 2,
|
||||||
|
Base: &models.PRBranchInfo{
|
||||||
|
Name: "master",
|
||||||
|
Sha: "pkg_base_sha",
|
||||||
|
Repo: &models.Repository{
|
||||||
|
Owner: &models.User{UserName: "org"},
|
||||||
|
Name: "pkg",
|
||||||
|
SSHURL: "ssh://git@src.opensuse.org/org/pkg.git",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Head: &models.PRBranchInfo{
|
||||||
|
Name: "branch_name",
|
||||||
|
Sha: "pkg_head_sha",
|
||||||
|
Repo: &models.Repository{
|
||||||
|
Owner: &models.User{UserName: "org"},
|
||||||
|
Name: "pkg",
|
||||||
|
SSHURL: "ssh://git@src.opensuse.org/org/pkg.git",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AllowMaintainerEdit: test.editable,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctl := gomock.NewController(t)
|
||||||
|
git := mock_common.NewMockGit(ctl)
|
||||||
|
test.setup(git, prjPR, pkgPR)
|
||||||
|
|
||||||
|
prset := &common.PRSet{
|
||||||
|
Config: test.config,
|
||||||
|
PRs: []*common.PRInfo{
|
||||||
|
{PR: prjPR},
|
||||||
|
{PR: pkgPR},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if res := prset.PrepareForMerge(git); res != test.expected {
|
||||||
|
t.Errorf("Expected %v, got %v", test.expected, res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPRMergeMock(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setup func(*mock_common.MockGit, *models.PullRequest, *models.PullRequest)
|
||||||
|
config *common.AutogitConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Success FF",
|
||||||
|
config: &common.AutogitConfig{
|
||||||
|
Organization: "org",
|
||||||
|
GitProjectName: "org/_ObsPrj#master",
|
||||||
|
MergeMode: common.MergeModeFF,
|
||||||
|
},
|
||||||
|
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
||||||
|
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "-m", gomock.Any(), prjPR.Head.Sha).Return(nil)
|
||||||
|
|
||||||
|
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin_pkg", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin_pkg", pkgPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "merge", "--ff", pkgPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "push", "origin_pkg")
|
||||||
|
m.EXPECT().GitExecOrPanic("_ObsPrj", "push", "origin")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Success Devel",
|
||||||
|
config: &common.AutogitConfig{
|
||||||
|
Organization: "org",
|
||||||
|
GitProjectName: "org/_ObsPrj#master",
|
||||||
|
MergeMode: common.MergeModeDevel,
|
||||||
|
},
|
||||||
|
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
||||||
|
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "-m", gomock.Any(), prjPR.Head.Sha).Return(nil)
|
||||||
|
|
||||||
|
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin_pkg", nil)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "checkout", "-B", "master", pkgPR.Head.Sha)
|
||||||
|
m.EXPECT().GitExecOrPanic("pkg", "push", "-f", "origin_pkg")
|
||||||
|
m.EXPECT().GitExecOrPanic("_ObsPrj", "push", "origin")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
prjPR := &models.PullRequest{
|
||||||
|
Index: 1,
|
||||||
|
Base: &models.PRBranchInfo{
|
||||||
|
Name: "master",
|
||||||
|
Sha: "prj_base_sha",
|
||||||
|
Repo: &models.Repository{
|
||||||
|
Owner: &models.User{UserName: "org"},
|
||||||
|
Name: "_ObsPrj",
|
||||||
|
SSHURL: "ssh://git@src.opensuse.org/org/_ObsPrj.git",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Head: &models.PRBranchInfo{
|
||||||
|
Sha: "prj_head_sha",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pkgPR := &models.PullRequest{
|
||||||
|
Index: 2,
|
||||||
|
Base: &models.PRBranchInfo{
|
||||||
|
Name: "master",
|
||||||
|
Sha: "pkg_base_sha",
|
||||||
|
Repo: &models.Repository{
|
||||||
|
Owner: &models.User{UserName: "org"},
|
||||||
|
Name: "pkg",
|
||||||
|
SSHURL: "ssh://git@src.opensuse.org/org/pkg.git",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Head: &models.PRBranchInfo{
|
||||||
|
Sha: "pkg_head_sha",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctl := gomock.NewController(t)
|
||||||
|
git := mock_common.NewMockGit(ctl)
|
||||||
|
reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl)
|
||||||
|
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
|
||||||
|
|
||||||
|
test.setup(git, prjPR, pkgPR)
|
||||||
|
|
||||||
|
prset := &common.PRSet{
|
||||||
|
Config: test.config,
|
||||||
|
PRs: []*common.PRInfo{
|
||||||
|
{PR: prjPR},
|
||||||
|
{PR: pkgPR},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := prset.Merge(reviewUnrequestMock, git); err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPRAddMergeCommit(t *testing.T) {
|
||||||
|
pkgPR := &models.PullRequest{
|
||||||
|
Index: 2,
|
||||||
|
Base: &models.PRBranchInfo{
|
||||||
|
Name: "master",
|
||||||
|
Sha: "pkg_base_sha",
|
||||||
|
Repo: &models.Repository{
|
||||||
|
Owner: &models.User{UserName: "org"},
|
||||||
|
Name: "pkg",
|
||||||
|
SSHURL: "ssh://git@src.opensuse.org/org/pkg.git",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Head: &models.PRBranchInfo{
|
||||||
|
Name: "branch_name",
|
||||||
|
Sha: "pkg_head_sha",
|
||||||
|
},
|
||||||
|
AllowMaintainerEdit: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &common.AutogitConfig{
|
||||||
|
Organization: "org",
|
||||||
|
GitProjectName: "org/_ObsPrj#master",
|
||||||
|
MergeMode: common.MergeModeReplace,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctl := gomock.NewController(t)
|
||||||
|
git := mock_common.NewMockGit(ctl)
|
||||||
|
|
||||||
|
git.EXPECT().GitExec("pkg", "merge", "--no-ff", "--no-commit", "-X", "theirs", pkgPR.Head.Sha).Return(nil)
|
||||||
|
git.EXPECT().GitExecOrPanic("pkg", "read-tree", "-m", pkgPR.Head.Sha)
|
||||||
|
git.EXPECT().GitExecOrPanic("pkg", "commit", "-m", gomock.Any())
|
||||||
|
git.EXPECT().GitExecOrPanic("pkg", "clean", "-fxd")
|
||||||
|
git.EXPECT().GitExecOrPanic("pkg", "push", "origin", "HEAD:branch_name")
|
||||||
|
git.EXPECT().GitExecWithOutputOrPanic("pkg", "rev-list", "-1", "HEAD").Return("new_head_sha")
|
||||||
|
|
||||||
|
prset := &common.PRSet{
|
||||||
|
Config: config,
|
||||||
|
PRs: []*common.PRInfo{
|
||||||
|
{PR: &models.PullRequest{}}, // prjgit at index 0
|
||||||
|
{PR: pkgPR}, // pkg at index 1
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if res := prset.AddMergeCommit(git, "origin", 1); !res {
|
||||||
|
t.Errorf("Expected true, got %v", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,24 +40,10 @@ 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 -b base_rm_c main
|
git checkout main
|
||||||
git clean -ffxd
|
git clean -ffxd
|
||||||
git rm pkgC
|
git submodule -q add -f ../pkgB1 pkgB1
|
||||||
git commit -q -m 'pkgC removed'
|
git commit -q -m "main adding pkgB1"
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Autogits Workflow Direct instances
|
|
||||||
Documentation=https://src.opensuse.org/git-workflow/autogits
|
|
||||||
|
|
||||||
@@ -20,5 +20,4 @@ PrivateTmp=yes
|
|||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
WantedBy=workflow-direct.target
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Autogits Workflow PR instances
|
|
||||||
Documentation=https://src.opensuse.org/git-workflow/autogits
|
|
||||||
|
|
||||||
|
|
||||||
@@ -20,5 +20,4 @@ PrivateTmp=yes
|
|||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
WantedBy=workflow-pr.target
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -94,6 +94,19 @@ Package Deletion Requests
|
|||||||
If you wish to re-add a package, create a new PrjGit PR which adds again the submodule on the branch that has the "-removed" suffix. The bot will automatically remove this suffix from the project branch in the pool.
|
If you wish to re-add a package, create a new PrjGit PR which adds again the submodule on the branch that has the "-removed" suffix. The bot will automatically remove this suffix from the project branch in the pool.
|
||||||
|
|
||||||
|
|
||||||
|
Merge Modes
|
||||||
|
-----------
|
||||||
|
|
||||||
|
| Merge Mode | Description
|
||||||
|
|------------|--------------------------------------------------------------------------------
|
||||||
|
| ff-only | Only allow --ff-only merges in the package branch. This is best suited for
|
||||||
|
| | devel projects and openSUSE Tumbleweed development, where history should be linear
|
||||||
|
| replace | Merge is done via `-X theirs` strategy and old files are removed in the merge.
|
||||||
|
| | This works well for downstream codestreams, like Leap, that would update their branch
|
||||||
|
| | using latest version.
|
||||||
|
| devel | No merge, just set the project branch to PR HEAD. This is suitable for downstream
|
||||||
|
| | projects like Leap during development cycle, where keeping maintenance history is not important
|
||||||
|
|
||||||
Labels
|
Labels
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|||||||
@@ -406,6 +406,12 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
|||||||
}
|
}
|
||||||
common.LogInfo("fetched PRSet of size:", len(prset.PRs))
|
common.LogInfo("fetched PRSet of size:", len(prset.PRs))
|
||||||
|
|
||||||
|
if !prset.PrepareForMerge(git) {
|
||||||
|
common.LogError("PRs are NOT mergeable.")
|
||||||
|
} else {
|
||||||
|
common.LogInfo("PRs are in mergeable state.")
|
||||||
|
}
|
||||||
|
|
||||||
prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo)
|
prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo)
|
||||||
prjGitPR, err := prset.GetPrjGitPR()
|
prjGitPR, err := prset.GetPrjGitPR()
|
||||||
if err == common.PRSet_PrjGitMissing {
|
if err == common.PRSet_PrjGitMissing {
|
||||||
|
|||||||
Reference in New Issue
Block a user