mirror of
https://github.com/apricote/releaser-pleaser.git
synced 2026-02-08 02:37:04 +00:00
feat: avoid pushing release branch only for rebasing
This commit is contained in:
parent
522693395f
commit
d486851fd7
3 changed files with 186 additions and 10 deletions
|
|
@ -193,8 +193,27 @@ func (r *Repository) Commit(_ context.Context, message string, author Author) (C
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) HasChangesWithRemote(ctx context.Context, branch string) (bool, error) {
|
// HasChangesWithRemote checks if the following two diffs are equal:
|
||||||
remoteRef, err := r.r.Reference(plumbing.NewRemoteReferenceName(remoteName, branch), false)
|
//
|
||||||
|
// - **Local**: remote/main..branch
|
||||||
|
// - **Remote**: (git merge-base remote/main remote/branch)..remote/branch
|
||||||
|
//
|
||||||
|
// This is done to avoid pushing when the only change would be a rebase of remote/branch onto the current remote/main.
|
||||||
|
func (r *Repository) HasChangesWithRemote(ctx context.Context, mainBranch, prBranch string) (bool, error) {
|
||||||
|
return r.hasChangesWithRemote(ctx,
|
||||||
|
plumbing.NewRemoteReferenceName(remoteName, mainBranch),
|
||||||
|
plumbing.NewBranchReferenceName(prBranch),
|
||||||
|
plumbing.NewRemoteReferenceName(remoteName, prBranch),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) hasChangesWithRemote(ctx context.Context, mainBranchRef, localPRBranchRef, remotePRBranchRef plumbing.ReferenceName) (bool, error) {
|
||||||
|
commitOnRemoteMain, err := r.commitFromRef(mainBranchRef)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
commitOnRemotePRBranch, err := r.commitFromRef(remotePRBranchRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "reference not found" {
|
if err.Error() == "reference not found" {
|
||||||
// No remote branch means that there are changes
|
// No remote branch means that there are changes
|
||||||
|
|
@ -204,29 +223,60 @@ func (r *Repository) HasChangesWithRemote(ctx context.Context, branch string) (b
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteCommit, err := r.r.CommitObject(remoteRef.Hash())
|
currentRemotePRMergeBase, err := r.mergeBase(commitOnRemoteMain, commitOnRemotePRBranch)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if currentRemotePRMergeBase == nil {
|
||||||
|
// If there is no merge base something weird has happened with the
|
||||||
|
// remote main branch, and we should definitely push updates.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteDiff, err := commitOnRemotePRBranch.PatchContext(ctx, currentRemotePRMergeBase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
localRef, err := r.r.Reference(plumbing.NewBranchReferenceName(branch), false)
|
commitOnLocalPRBranch, err := r.commitFromRef(localPRBranchRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
localCommit, err := r.r.CommitObject(localRef.Hash())
|
localDiff, err := commitOnRemoteMain.PatchContext(ctx, commitOnLocalPRBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
diff, err := localCommit.PatchContext(ctx, remoteCommit)
|
return remoteDiff.String() == localDiff.String(), nil
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasChanges := len(diff.FilePatches()) > 0
|
func (r *Repository) commitFromRef(refName plumbing.ReferenceName) (*object.Commit, error) {
|
||||||
|
ref, err := r.r.Reference(refName, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return hasChanges, nil
|
commit, err := r.r.CommitObject(ref.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return commit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) mergeBase(a, b *object.Commit) (*object.Commit, error) {
|
||||||
|
mergeBases, err := a.MergeBase(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(mergeBases) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// :shrug: We dont really care which commit we pick, at worst we do an unnecessary push.
|
||||||
|
return mergeBases[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) ForcePush(ctx context.Context, branch string) error {
|
func (r *Repository) ForcePush(ctx context.Context, branch string) error {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAuthor_signature(t *testing.T) {
|
func TestAuthor_signature(t *testing.T) {
|
||||||
|
|
@ -44,3 +47,126 @@ func TestAuthor_String(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testMainBranch = "main"
|
||||||
|
const testPRBranch = "releaser-pleaser"
|
||||||
|
|
||||||
|
func TestRepository_HasChangesWithRemote(t *testing.T) {
|
||||||
|
// go-git/v5 has a bug where it tries to delete the repo root dir (".") multiple times if there is no file left in it.
|
||||||
|
// this happens while switching branches in worktree.go rmFileAndDirsIfEmpty.
|
||||||
|
// TODO: Fix bug upstream
|
||||||
|
// For now I just make sure that there is always at least one file left in the dir by adding an empty "README.md" in the test util.
|
||||||
|
|
||||||
|
mainBranchRef := plumbing.NewBranchReferenceName(testMainBranch)
|
||||||
|
localPRBranchRef := plumbing.NewBranchReferenceName(testPRBranch)
|
||||||
|
remotePRBranchRef := plumbing.NewBranchReferenceName("remote/" + testPRBranch)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
repo TestRepo
|
||||||
|
want bool
|
||||||
|
wantErr assert.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no remote pr branch",
|
||||||
|
repo: WithTestRepo(
|
||||||
|
WithCommit(
|
||||||
|
"chore: release v1.0.0",
|
||||||
|
WithFile("VERSION", "v1.0.0"),
|
||||||
|
),
|
||||||
|
WithCommit(
|
||||||
|
"chore: release v1.1.0",
|
||||||
|
OnBranch(mainBranchRef),
|
||||||
|
AsNewBranch(localPRBranchRef),
|
||||||
|
WithFile("VERSION", "v1.1.0"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
want: true,
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remote pr branch matches local",
|
||||||
|
repo: WithTestRepo(
|
||||||
|
WithCommit(
|
||||||
|
"chore: release v1.0.0",
|
||||||
|
WithFile("VERSION", "v1.0.0"),
|
||||||
|
),
|
||||||
|
WithCommit(
|
||||||
|
"chore: release v1.1.0",
|
||||||
|
OnBranch(mainBranchRef),
|
||||||
|
AsNewBranch(remotePRBranchRef),
|
||||||
|
WithFile("VERSION", "v1.1.0"),
|
||||||
|
),
|
||||||
|
WithCommit(
|
||||||
|
"chore: release v1.1.0",
|
||||||
|
OnBranch(mainBranchRef),
|
||||||
|
AsNewBranch(localPRBranchRef),
|
||||||
|
WithFile("VERSION", "v1.1.0"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
want: false,
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remote pr only needs rebase",
|
||||||
|
repo: WithTestRepo(
|
||||||
|
WithCommit(
|
||||||
|
"chore: release v1.0.0",
|
||||||
|
WithFile("VERSION", "v1.0.0"),
|
||||||
|
),
|
||||||
|
WithCommit(
|
||||||
|
"chore: release v1.1.0",
|
||||||
|
OnBranch(mainBranchRef),
|
||||||
|
AsNewBranch(remotePRBranchRef),
|
||||||
|
WithFile("VERSION", "v1.1.0"),
|
||||||
|
),
|
||||||
|
WithCommit(
|
||||||
|
"feat: new feature on remote",
|
||||||
|
OnBranch(mainBranchRef),
|
||||||
|
WithFile("feature", "yes"),
|
||||||
|
),
|
||||||
|
WithCommit(
|
||||||
|
"chore: release v1.1.0",
|
||||||
|
OnBranch(mainBranchRef),
|
||||||
|
AsNewBranch(localPRBranchRef),
|
||||||
|
WithFile("VERSION", "v1.1.0"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
want: false,
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "needs update",
|
||||||
|
repo: WithTestRepo(
|
||||||
|
WithCommit(
|
||||||
|
"chore: release v1.0.0",
|
||||||
|
WithFile("VERSION", "v1.0.0"),
|
||||||
|
),
|
||||||
|
WithCommit(
|
||||||
|
"chore: release v1.1.0",
|
||||||
|
OnBranch(mainBranchRef),
|
||||||
|
AsNewBranch(remotePRBranchRef),
|
||||||
|
WithFile("VERSION", "v1.1.0"),
|
||||||
|
),
|
||||||
|
WithCommit(
|
||||||
|
"chore: release v1.2.0",
|
||||||
|
OnBranch(mainBranchRef),
|
||||||
|
AsNewBranch(localPRBranchRef),
|
||||||
|
WithFile("VERSION", "v1.2.0"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
want: false,
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
repo := tt.repo(t)
|
||||||
|
got, err := repo.hasChangesWithRemote(context.Background(), mainBranchRef, localPRBranchRef, remotePRBranchRef)
|
||||||
|
if !tt.wantErr(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -269,7 +269,7 @@ func (rp *ReleaserPleaser) runReconcileReleasePR(ctx context.Context) error {
|
||||||
logger.InfoContext(ctx, "created release commit", "commit.hash", releaseCommit.Hash, "commit.message", releaseCommit.Message, "commit.author", releaseCommitAuthor)
|
logger.InfoContext(ctx, "created release commit", "commit.hash", releaseCommit.Hash, "commit.message", releaseCommit.Message, "commit.author", releaseCommitAuthor)
|
||||||
|
|
||||||
// Check if anything changed in comparison to the remote branch (if exists)
|
// Check if anything changed in comparison to the remote branch (if exists)
|
||||||
newReleasePRChanges, err := repo.HasChangesWithRemote(ctx, rpBranch)
|
newReleasePRChanges, err := repo.HasChangesWithRemote(ctx, rp.targetBranch, rpBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue