145 lines
4.9 KiB
Go
145 lines
4.9 KiB
Go
package executor
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestForcePushUsesLease(t *testing.T) {
|
|
runner := &fakeGitRunner{responses: map[string]gitResponse{
|
|
"push --force-with-lease origin main": {out: "ok"},
|
|
}}
|
|
if err := forcePush(context.Background(), runner, "origin", "main"); err != nil {
|
|
t.Fatalf("forcePush: %v", err)
|
|
}
|
|
// Never a bare --force: the lease protects concurrent pushes.
|
|
if len(runner.calls) != 1 || runner.calls[0] != "push --force-with-lease origin main" {
|
|
t.Fatalf("calls = %v", runner.calls)
|
|
}
|
|
for _, call := range runner.calls {
|
|
if strings.Contains(call, "--force ") || strings.HasSuffix(call, " --force") {
|
|
t.Fatalf("unexpected bare --force: %q", call)
|
|
}
|
|
}
|
|
}
|
|
|
|
// lfsAdvisory is the git-lfs note that used to be mistaken for the cause.
|
|
const lfsAdvisory = `Locking support detected on remote "origin". Consider enabling it with:
|
|
$ git config lfs.https://gitea.example.com/a/b.git/info/lfs.locksverify true
|
|
`
|
|
|
|
func TestForcePushExplainsLeaseRejection(t *testing.T) {
|
|
runner := &fakeGitRunner{responses: map[string]gitResponse{
|
|
"push --force-with-lease origin main": {
|
|
errOut: lfsAdvisory + " ! [rejected] main -> main (stale info)\n",
|
|
err: errors.New("exit status 1"),
|
|
},
|
|
}}
|
|
err := forcePush(context.Background(), runner, "origin", "main")
|
|
if err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
msg := err.Error()
|
|
for _, want := range []string{"租约校验失败", "--force-with-lease=main:", "回滚"} {
|
|
if !strings.Contains(msg, want) {
|
|
t.Errorf("message missing %q:\n%s", want, msg)
|
|
}
|
|
}
|
|
// The git-lfs advice is noise here and must not lead the message.
|
|
if strings.Contains(msg, "Locking support detected") {
|
|
t.Errorf("LFS advisory leaked into the lease message:\n%s", msg)
|
|
}
|
|
}
|
|
|
|
func TestForcePushExplainsLFSMissing(t *testing.T) {
|
|
stderr := lfsAdvisory +
|
|
"Git LFS upload failed: 0% (0/1)\n" +
|
|
" (missing) doc/ffmpeg.zip (6080e34165fcb0261f979bb1cef26c5fce616e2a416384b50bcce956aa265018)\n" +
|
|
"hint: Your push was rejected due to missing or corrupt local objects.\n" +
|
|
"hint: You can disable this check with: `git config lfs.allowincompletepush true`\n"
|
|
runner := &fakeGitRunner{responses: map[string]gitResponse{
|
|
"push --force-with-lease origin main": {errOut: stderr, err: errors.New("exit status 1")},
|
|
}}
|
|
err := forcePush(context.Background(), runner, "origin", "main")
|
|
if err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
msg := err.Error()
|
|
for _, want := range []string{
|
|
"LFS 完整性检查",
|
|
"doc/ffmpeg.zip",
|
|
"lfs.allowincompletepush=true",
|
|
} {
|
|
if !strings.Contains(msg, want) {
|
|
t.Errorf("message missing %q:\n%s", want, msg)
|
|
}
|
|
}
|
|
// The lease wording would mislead: LFS, not the remote, blocked this push.
|
|
if strings.Contains(msg, "租约") {
|
|
t.Errorf("LFS failure was reported as a lease problem:\n%s", msg)
|
|
}
|
|
if strings.Contains(msg, "Locking support detected") {
|
|
t.Errorf("LFS advisory leaked into the message:\n%s", msg)
|
|
}
|
|
}
|
|
|
|
func TestForcePushExplainsProtectedBranch(t *testing.T) {
|
|
runner := &fakeGitRunner{responses: map[string]gitResponse{
|
|
"push --force-with-lease origin main": {
|
|
errOut: "remote: error: GH006: protected branch update failed\nremote: pre-receive hook declined\n",
|
|
err: errors.New("exit status 1"),
|
|
},
|
|
}}
|
|
err := forcePush(context.Background(), runner, "origin", "main")
|
|
if err == nil || !strings.Contains(err.Error(), "分支保护") {
|
|
t.Fatalf("err = %v, want a branch-protection message", err)
|
|
}
|
|
if strings.Contains(err.Error(), "租约") {
|
|
t.Errorf("protected branch was reported as a lease problem: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIsRejectedPush(t *testing.T) {
|
|
yes := []string{
|
|
"! [rejected] main -> main (stale info)",
|
|
"remote: pre-receive hook declined",
|
|
"protected branch hook declined",
|
|
}
|
|
for _, s := range yes {
|
|
if !isRejectedPush(s) {
|
|
t.Errorf("isRejectedPush(%q) = false, want true", s)
|
|
}
|
|
}
|
|
if isRejectedPush("fatal: unable to access remote") {
|
|
t.Error("network errors are not lease rejections")
|
|
}
|
|
// An LFS failure is not a remote-side rejection.
|
|
if isRejectedPush("Git LFS upload failed:\n (missing) a.zip (abc)") {
|
|
t.Error("LFS failures must not be classified as lease rejections")
|
|
}
|
|
}
|
|
|
|
func TestForcePushReportsNetworkError(t *testing.T) {
|
|
runner := &fakeGitRunner{responses: map[string]gitResponse{
|
|
"push --force-with-lease origin main": {
|
|
errOut: "fatal: unable to access remote", err: errors.New("exit 128"),
|
|
},
|
|
}}
|
|
err := forcePush(context.Background(), runner, "origin", "main")
|
|
if err == nil || !strings.Contains(err.Error(), "git push 失败") {
|
|
t.Fatalf("err = %v, want a git push failure", err)
|
|
}
|
|
}
|
|
|
|
func TestFirstMissingObject(t *testing.T) {
|
|
stderr := "Git LFS upload failed:\n (missing) doc/a.zip (abc123)\n (missing) upx.exe (def456)"
|
|
if got := firstMissingObject(stderr); got != "doc/a.zip" {
|
|
t.Errorf("firstMissingObject = %q", got)
|
|
}
|
|
if got := firstMissingObject("no entries"); got != "未知对象" {
|
|
t.Errorf("firstMissingObject = %q, want a placeholder", got)
|
|
}
|
|
}
|