mirror of
https://gitea.com/gitea/tea.git
synced 2026-09-10 07:26:33 -04:00
test(pulls): cover local branch deletion with a git fixture
The other tests chdir into an empty temp dir, so RepoForWorkdir fails and PullDeleteBranch's branch-matching and checkout path never runs. A real repository fixture works offline: the git backend shells out to the git binary, GetRemote matches a remote by parsed host and path rather than contacting it, and a remote-tracking ref can be written with update-ref instead of fetched. Covers a branch that is not checked out, one that is, a diverged local branch being left alone, no matching local branch, and a missing base branch being reported rather than returned. The fixture pins --initial-branch=main and isolates GIT_CONFIG_GLOBAL and commit.gpgsign (see #1072). These tests call t.Chdir, so they cannot run in parallel. Signed-off-by: Jan Baer <jan.s.baer@googlemail.com>
This commit is contained in:
parent
5aedf30e7b
commit
1472589a93
162
modules/task/pull_delete_branch_local_test.go
Normal file
162
modules/task/pull_delete_branch_local_test.go
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package task
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/tea/modules/config"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// headCloneURL is never contacted: GetRemote matches on parsed host and path.
|
||||
const headCloneURL = "https://example.invalid/contributor/tea.git"
|
||||
|
||||
func git(t *testing.T, dir string, args ...string) string {
|
||||
t.Helper()
|
||||
cmd := exec.Command("git", append([]string{"-C", dir}, args...)...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoErrorf(t, err, "git %v failed: %s", args, out)
|
||||
return strings.TrimSpace(string(out))
|
||||
}
|
||||
|
||||
func commit(t *testing.T, dir, name string) string {
|
||||
t.Helper()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, name), []byte(name), 0o644))
|
||||
git(t, dir, "add", name)
|
||||
git(t, dir, "commit", "-m", name)
|
||||
return git(t, dir, "rev-parse", "HEAD")
|
||||
}
|
||||
|
||||
// gitFixture builds a repo with 'main' and 'feature-x' plus a remote-tracking
|
||||
// ref, and returns the path and the feature-x commit. HEAD is left on main.
|
||||
func gitFixture(t *testing.T) (dir, sha string) {
|
||||
t.Helper()
|
||||
|
||||
// A global commit.gpgsign or init.defaultBranch would break the fixture (#1072).
|
||||
t.Setenv("GIT_CONFIG_GLOBAL", filepath.Join(t.TempDir(), ".gitconfig"))
|
||||
|
||||
dir = filepath.Join(t.TempDir(), "repo")
|
||||
require.NoError(t, os.MkdirAll(dir, 0o755))
|
||||
|
||||
// The default name varies by git version, and the PR names 'main' as base.
|
||||
git(t, dir, "init", "--initial-branch=main")
|
||||
git(t, dir, "config", "user.email", "test@example.com")
|
||||
git(t, dir, "config", "user.name", "Test User")
|
||||
git(t, dir, "config", "commit.gpgsign", "false")
|
||||
|
||||
commit(t, dir, "base")
|
||||
git(t, dir, "checkout", "-b", "feature-x")
|
||||
sha = commit(t, dir, "work")
|
||||
|
||||
git(t, dir, "remote", "add", "origin", headCloneURL)
|
||||
git(t, dir, "update-ref", "refs/remotes/origin/feature-x", sha)
|
||||
git(t, dir, "checkout", "main")
|
||||
|
||||
return dir, sha
|
||||
}
|
||||
|
||||
func branchExists(t *testing.T, dir, branch string) bool {
|
||||
t.Helper()
|
||||
return exec.Command("git", "-C", dir, "rev-parse", "--verify", "--quiet",
|
||||
"refs/heads/"+branch).Run() == nil
|
||||
}
|
||||
|
||||
func currentBranch(t *testing.T, dir string) string {
|
||||
t.Helper()
|
||||
return git(t, dir, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
}
|
||||
|
||||
// mergedPR serves the PR for a gitFixture repo and accepts the branch DELETE.
|
||||
func mergedPR(t *testing.T, sha string) *httptest.Server {
|
||||
t.Helper()
|
||||
var ignored string
|
||||
return deleteBranchServer(t, prJSON("contributor", "tea", "feature-x", sha, true), &ignored)
|
||||
}
|
||||
|
||||
func deleteFixtureBranch(t *testing.T, server *httptest.Server) error {
|
||||
t.Helper()
|
||||
return PullDeleteBranch(t.Context(), &config.Login{
|
||||
Name: "test",
|
||||
URL: server.URL,
|
||||
Token: "secret-token",
|
||||
VersionCheck: false,
|
||||
}, "owner", "repo", 7, nil)
|
||||
}
|
||||
|
||||
// These tests change the working directory, so they cannot run in parallel.
|
||||
|
||||
func TestPullDeleteBranchRemovesLocalBranch(t *testing.T) {
|
||||
dir, sha := gitFixture(t)
|
||||
server := mergedPR(t, sha)
|
||||
defer server.Close()
|
||||
t.Chdir(dir)
|
||||
|
||||
require.NoError(t, deleteFixtureBranch(t, server))
|
||||
|
||||
assert.False(t, branchExists(t, dir, "feature-x"), "local branch should be gone")
|
||||
assert.Equal(t, "main", currentBranch(t, dir), "HEAD should not have moved")
|
||||
}
|
||||
|
||||
func TestPullDeleteBranchChecksOutBaseWhenOnTheBranch(t *testing.T) {
|
||||
dir, sha := gitFixture(t)
|
||||
git(t, dir, "checkout", "feature-x")
|
||||
server := mergedPR(t, sha)
|
||||
defer server.Close()
|
||||
t.Chdir(dir)
|
||||
|
||||
require.NoError(t, deleteFixtureBranch(t, server))
|
||||
|
||||
// git won't delete the checked-out branch, so base comes first.
|
||||
assert.Equal(t, "main", currentBranch(t, dir))
|
||||
assert.False(t, branchExists(t, dir, "feature-x"))
|
||||
}
|
||||
|
||||
// The guard that matters: unpushed commits must survive.
|
||||
func TestPullDeleteBranchKeepsDivergedLocalBranch(t *testing.T) {
|
||||
dir, sha := gitFixture(t)
|
||||
git(t, dir, "checkout", "feature-x")
|
||||
local := commit(t, dir, "unpushed")
|
||||
git(t, dir, "checkout", "main")
|
||||
require.NotEqual(t, sha, local)
|
||||
|
||||
server := mergedPR(t, sha)
|
||||
defer server.Close()
|
||||
t.Chdir(dir)
|
||||
|
||||
require.NoError(t, deleteFixtureBranch(t, server))
|
||||
|
||||
require.True(t, branchExists(t, dir, "feature-x"), "diverged branch must not be deleted")
|
||||
assert.Equal(t, local, git(t, dir, "rev-parse", "feature-x"), "unpushed commit must survive")
|
||||
}
|
||||
|
||||
func TestPullDeleteBranchWithoutMatchingLocalBranch(t *testing.T) {
|
||||
dir, sha := gitFixture(t)
|
||||
git(t, dir, "branch", "-D", "feature-x")
|
||||
server := mergedPR(t, sha)
|
||||
defer server.Close()
|
||||
t.Chdir(dir)
|
||||
|
||||
assert.NoError(t, deleteFixtureBranch(t, server))
|
||||
}
|
||||
|
||||
// The remote is already gone by then, so a failed checkout is reported, not returned.
|
||||
func TestPullDeleteBranchSurvivesMissingBaseBranch(t *testing.T) {
|
||||
dir, sha := gitFixture(t)
|
||||
git(t, dir, "checkout", "feature-x")
|
||||
git(t, dir, "branch", "-D", "main")
|
||||
server := mergedPR(t, sha)
|
||||
defer server.Close()
|
||||
t.Chdir(dir)
|
||||
|
||||
assert.NoError(t, deleteFixtureBranch(t, server))
|
||||
assert.True(t, branchExists(t, dir, "feature-x"), "branch stays when the checkout fails")
|
||||
}
|
||||
Loading…
Reference in a new issue