mirror of
https://gitea.com/gitea/tea.git
synced 2026-06-05 18:58:43 +02:00
Use git command instead of go git (#1005)
Remove go git library because it doesn't support sha256 repository but have an interface so that we could have other backend for the future. Reviewed-on: https://gitea.com/gitea/tea/pulls/1005 Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com>
This commit is contained in:
@@ -34,6 +34,7 @@ PACKAGES ?= $(shell $(GO) list ./... | grep -v '^gitea.dev/tea/tests')
|
||||
UNIT_PACKAGES ?= $(PACKAGES)
|
||||
INTEGRATION_PACKAGES ?= $(shell $(GO) list ./tests/... 2>/dev/null)
|
||||
INTEGRATION_TEST_TAGS ?= testtools
|
||||
INTEGRATION_TEST_GOFLAGS ?= -v
|
||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||
|
||||
# OS specific vars.
|
||||
@@ -103,7 +104,7 @@ unit-test:
|
||||
.PHONY: integration-test
|
||||
integration-test:
|
||||
@if [ -n "$(INTEGRATION_PACKAGES)" ]; then \
|
||||
$(GO) test -tags='$(INTEGRATION_TEST_TAGS)' $(INTEGRATION_PACKAGES); \
|
||||
$(GO) test $(INTEGRATION_TEST_GOFLAGS) -tags='$(INTEGRATION_TEST_TAGS)' $(INTEGRATION_PACKAGES); \
|
||||
else \
|
||||
echo "No integration test packages found"; \
|
||||
fi
|
||||
|
||||
+16
-19
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -15,9 +16,6 @@ import (
|
||||
"gitea.dev/tea/modules/context"
|
||||
tea_git "gitea.dev/tea/modules/git"
|
||||
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
@@ -572,33 +570,32 @@ func TestExpandPlaceholders(t *testing.T) {
|
||||
|
||||
t.Run("replaces branch from local repo HEAD", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
repo, err := gogit.PlainInit(tmpDir, false)
|
||||
require.NoError(t, err)
|
||||
runGit := func(args ...string) {
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = tmpDir
|
||||
require.NoError(t, cmd.Run())
|
||||
}
|
||||
|
||||
runGit("init")
|
||||
runGit("config", "user.email", "test@test.com")
|
||||
runGit("config", "user.name", "test")
|
||||
|
||||
// Create an initial commit so HEAD points to a branch.
|
||||
wt, err := repo.Worktree()
|
||||
require.NoError(t, err)
|
||||
tmpFile := filepath.Join(tmpDir, "init.txt")
|
||||
require.NoError(t, os.WriteFile(tmpFile, []byte("init"), 0o644))
|
||||
_, err = wt.Add("init.txt")
|
||||
require.NoError(t, err)
|
||||
_, err = wt.Commit("initial commit", &gogit.CommitOptions{
|
||||
Author: &object.Signature{Name: "test", Email: "test@test.com"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
runGit("add", "init.txt")
|
||||
runGit("commit", "-m", "initial commit")
|
||||
|
||||
// Create and checkout a feature branch.
|
||||
headRef, err := repo.Head()
|
||||
runGit("checkout", "-b", "feature/my-branch")
|
||||
|
||||
repo, err := tea_git.RepoFromPath(tmpDir)
|
||||
require.NoError(t, err)
|
||||
branchRef := plumbing.NewBranchReferenceName("feature/my-branch")
|
||||
ref := plumbing.NewHashReference(branchRef, headRef.Hash())
|
||||
require.NoError(t, repo.Storer.SetReference(ref))
|
||||
require.NoError(t, wt.Checkout(&gogit.CheckoutOptions{Branch: branchRef}))
|
||||
|
||||
ctx := &context.TeaContext{
|
||||
Owner: "alice",
|
||||
Repo: "proj",
|
||||
LocalRepo: &tea_git.TeaRepo{Repository: repo},
|
||||
LocalRepo: repo,
|
||||
}
|
||||
result := expandPlaceholders("/repos/{owner}/{repo}/branches/{branch}", ctx)
|
||||
assert.Equal(t, "/repos/alice/proj/branches/feature/my-branch", result)
|
||||
|
||||
@@ -13,7 +13,6 @@ require (
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
||||
github.com/enescakir/emoji v1.0.0
|
||||
github.com/go-authgate/sdk-go v0.11.0
|
||||
github.com/go-git/go-git/v5 v5.19.1
|
||||
github.com/muesli/termenv v0.16.0
|
||||
github.com/olekukonko/tablewriter v1.1.4
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
@@ -30,10 +29,7 @@ require (
|
||||
require (
|
||||
charm.land/bubbles/v2 v2.1.0 // indirect
|
||||
charm.land/bubbletea/v2 v2.0.6 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/42wim/httpsig v1.2.4 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.4.1 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.24.1 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
@@ -51,27 +47,20 @@ require (
|
||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/danieljoos/wincred v1.2.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dlclark/regexp2 v1.12.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.9.0 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/hashicorp/go-version v1.9.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v1.6.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
@@ -82,22 +71,20 @@ require (
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.3.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.8 // indirect
|
||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yuin/goldmark v1.8.2 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.6 // indirect
|
||||
github.com/zalando/go-keyring v0.2.8 // indirect
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
||||
retract v1.3.3 // accidental release, tag deleted
|
||||
|
||||
@@ -12,19 +12,12 @@ code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
|
||||
code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
||||
code.gitea.io/sdk/gitea v0.25.1 h1:yywxWwoV+SdjHtbC6unBiXojWdZOtoHuGhEazEXeWuE=
|
||||
code.gitea.io/sdk/gitea v0.25.1/go.mod h1:uDFWYBU8dgZsgOHwe6C/6olxvf8FHguNB3wW1i83fgg=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
gitea.com/noerw/unidiff-comments v0.0.0-20220822113322-50f4daa0e35c h1:8fTkq2UaVkLHZCF+iB4wTxINmVAToe2geZGayk9LMbA=
|
||||
gitea.com/noerw/unidiff-comments v0.0.0-20220822113322-50f4daa0e35c/go.mod h1:Fc8iyPm4NINRWujeIk2bTfcbGc4ZYY29/oMAAGcr4qI=
|
||||
github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
|
||||
github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps=
|
||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
||||
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
@@ -33,12 +26,8 @@ github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6
|
||||
github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI=
|
||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
@@ -81,14 +70,11 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
|
||||
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -100,34 +86,18 @@ github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz
|
||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/enescakir/emoji v1.0.0 h1:W+HsNql8swfCQFtioDGDHCHri8nudlK1n5p2rHCJoog=
|
||||
github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkKp+WKFD0=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-authgate/sdk-go v0.11.0 h1:ZTfJ0rzeDn4QBqAmF9VKS3CqlKhE8+0tJxg8OGNtIzo=
|
||||
github.com/go-authgate/sdk-go v0.11.0/go.mod h1:sa0ige5wtayj2WcnXlxa8wGuyi5z/c/chc0mXPJTl/Q=
|
||||
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
|
||||
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
|
||||
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
@@ -136,13 +106,7 @@ github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaX
|
||||
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
||||
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -174,17 +138,13 @@ github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8=
|
||||
github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
|
||||
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
|
||||
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
||||
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
@@ -192,18 +152,11 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||
github.com/seletskiy/tplutil v0.0.0-20200921103632-f880f6245597 h1:nZY1S2jo+VtDrUfjO9XYI137O41hhRkxZNV5Fb5ixCA=
|
||||
github.com/seletskiy/tplutil v0.0.0-20200921103632-f880f6245597/go.mod h1:F8CBHSOjnzjx9EeXyWJTAzJyVxN+Y8JH2WjLMn4utiw=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
||||
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
@@ -211,8 +164,6 @@ github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7v
|
||||
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
|
||||
github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c=
|
||||
github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -226,7 +177,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||
@@ -238,7 +188,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
@@ -249,12 +198,7 @@ golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -262,7 +206,6 @@ golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -274,13 +217,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build testtools
|
||||
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"gitea.dev/tea/modules/utils"
|
||||
|
||||
"charm.land/huh/v2"
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
"github.com/urfave/cli/v3"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
@@ -112,7 +111,7 @@ func InitCommand(cmd *cli.Command) (*TeaContext, error) {
|
||||
}
|
||||
|
||||
if c.LocalRepo, c.Login, c.RepoSlug, err = contextFromLocalRepo(repoPath, remoteFlag, extraLogins); err != nil {
|
||||
if err == errNotAGiteaRepo || err == gogit.ErrRepositoryNotExists {
|
||||
if err == errNotAGiteaRepo || err == git.ErrRepositoryNotExists {
|
||||
// we can deal with that, commands needing the optional values use ctx.Ensure()
|
||||
} else {
|
||||
return nil, err
|
||||
|
||||
+43
-48
@@ -7,69 +7,64 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/tea/modules/utils"
|
||||
|
||||
git_transport "github.com/go-git/go-git/v5/plumbing/transport"
|
||||
gogit_http "github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
gogit_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type pwCallback = func(string) (string, error)
|
||||
|
||||
// GetAuthForURL returns the appropriate AuthMethod to be used in Push() / Pull()
|
||||
// operations depending on the protocol, and prompts the user for credentials if
|
||||
// necessary.
|
||||
func GetAuthForURL(remoteURL *url.URL, authToken, keyFile string, passwordCallback pwCallback) (git_transport.AuthMethod, error) {
|
||||
// GetAuthForURL returns backend-agnostic auth settings for git network operations.
|
||||
func GetAuthForURL(remoteURL *url.URL, authToken, keyFile string, passwordCallback pwCallback) (*AuthMethod, error) {
|
||||
switch remoteURL.Scheme {
|
||||
case "http", "https":
|
||||
// gitea supports push/pull via app token as username.
|
||||
return &gogit_http.BasicAuth{Password: "", Username: authToken}, nil
|
||||
|
||||
return &AuthMethod{Scheme: remoteURL.Scheme, Username: authToken}, nil
|
||||
case "ssh":
|
||||
// try to select right key via ssh-agent. if it fails, try to read a key manually
|
||||
user := remoteURL.User.Username()
|
||||
auth, err := gogit_ssh.DefaultAuthBuilder(user)
|
||||
if err != nil {
|
||||
signer, err2 := readSSHPrivKey(keyFile, passwordCallback)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
auth = &gogit_ssh.PublicKeys{User: user, Signer: signer}
|
||||
if keyFile == "" {
|
||||
return &AuthMethod{Scheme: remoteURL.Scheme, Username: remoteURL.User.Username()}, nil
|
||||
}
|
||||
expandedKeyFile, err := utils.AbsPathWithExpansion(keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKey, err := os.ReadFile(expandedKeyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not read ssh key '%s'", expandedKeyFile)
|
||||
}
|
||||
return auth, nil
|
||||
}
|
||||
return nil, fmt.Errorf("don't know how to handle url scheme %v", remoteURL.Scheme)
|
||||
}
|
||||
|
||||
func readSSHPrivKey(keyFile string, passwordCallback pwCallback) (sig ssh.Signer, err error) {
|
||||
if keyFile != "" {
|
||||
keyFile, err = utils.AbsPathWithExpansion(keyFile)
|
||||
} else {
|
||||
keyFile, err = utils.AbsPathWithExpansion("~/.ssh/id_rsa")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKey, err := os.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not read ssh key '%s'", keyFile)
|
||||
}
|
||||
sig, err = ssh.ParsePrivateKey(sshKey)
|
||||
if _, ok := err.(*ssh.PassphraseMissingError); ok && passwordCallback != nil {
|
||||
// allow for up to 3 password attempts
|
||||
for i := 0; i < 3; i++ {
|
||||
var pass string
|
||||
pass, err = passwordCallback(keyFile)
|
||||
if err != nil {
|
||||
auth := &AuthMethod{
|
||||
Scheme: remoteURL.Scheme,
|
||||
Username: remoteURL.User.Username(),
|
||||
KeyFile: expandedKeyFile,
|
||||
}
|
||||
if _, err := ssh.ParsePrivateKey(sshKey); err == nil {
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
if _, ok := err.(*ssh.PassphraseMissingError); ok {
|
||||
if passwordCallback == nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err = ssh.ParsePrivateKeyWithPassphrase(sshKey, []byte(pass))
|
||||
if err == nil {
|
||||
break
|
||||
for i := 0; i < 3; i++ {
|
||||
pass, err := passwordCallback(expandedKeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := ssh.ParsePrivateKeyWithPassphrase(sshKey, []byte(pass)); err == nil {
|
||||
auth.KeyPassphrase = pass
|
||||
return auth, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
default:
|
||||
return nil, fmt.Errorf("don't know how to handle url scheme %v", remoteURL.Scheme)
|
||||
}
|
||||
return sig, err
|
||||
}
|
||||
|
||||
func shellQuote(s string) string {
|
||||
return "'" + strings.ReplaceAll(s, "'", "'\\''") + "'"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var backendRegistry = struct {
|
||||
sync.RWMutex
|
||||
current string
|
||||
backends map[string]Backend
|
||||
}{
|
||||
backends: make(map[string]Backend),
|
||||
}
|
||||
|
||||
func init() {
|
||||
mustRegisterBackend(cliBackend{})
|
||||
mustUseBackend("cli")
|
||||
}
|
||||
|
||||
// RegisterBackend makes a git backend available for later switching.
|
||||
func RegisterBackend(backend Backend) error {
|
||||
if backend == nil {
|
||||
return fmt.Errorf("git backend is nil")
|
||||
}
|
||||
name := backend.Name()
|
||||
if name == "" {
|
||||
return fmt.Errorf("git backend name is empty")
|
||||
}
|
||||
|
||||
backendRegistry.Lock()
|
||||
defer backendRegistry.Unlock()
|
||||
backendRegistry.backends[name] = backend
|
||||
if backendRegistry.current == "" {
|
||||
backendRegistry.current = name
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustRegisterBackend(backend Backend) {
|
||||
if err := RegisterBackend(backend); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// UseBackend switches the active git backend implementation.
|
||||
func UseBackend(name string) error {
|
||||
backendRegistry.Lock()
|
||||
defer backendRegistry.Unlock()
|
||||
if _, ok := backendRegistry.backends[name]; !ok {
|
||||
return fmt.Errorf("git backend %q is not registered", name)
|
||||
}
|
||||
backendRegistry.current = name
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustUseBackend(name string) {
|
||||
if err := UseBackend(name); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// CurrentBackendName returns the active backend name.
|
||||
func CurrentBackendName() string {
|
||||
backendRegistry.RLock()
|
||||
defer backendRegistry.RUnlock()
|
||||
return backendRegistry.current
|
||||
}
|
||||
|
||||
// RegisteredBackends returns all registered backend names.
|
||||
func RegisteredBackends() []string {
|
||||
backendRegistry.RLock()
|
||||
defer backendRegistry.RUnlock()
|
||||
out := make([]string, 0, len(backendRegistry.backends))
|
||||
for name := range backendRegistry.backends {
|
||||
out = append(out, name)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func currentBackend() Backend {
|
||||
backendRegistry.RLock()
|
||||
defer backendRegistry.RUnlock()
|
||||
return backendRegistry.backends[backendRegistry.current]
|
||||
}
|
||||
|
||||
func setBackendForTesting(t testingT, backend Backend) {
|
||||
t.Helper()
|
||||
prev := CurrentBackendName()
|
||||
mustRegisterBackend(backend)
|
||||
mustUseBackend(backend.Name())
|
||||
t.Cleanup(func() { mustUseBackend(prev) })
|
||||
}
|
||||
|
||||
type testingT interface {
|
||||
Cleanup(func())
|
||||
Helper()
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fakeBackend struct{}
|
||||
|
||||
type fakeRepoBackend struct {
|
||||
workTree string
|
||||
}
|
||||
|
||||
func (fakeBackend) Name() string { return "fake" }
|
||||
|
||||
func (fakeBackend) Open(path string) (RepositoryBackend, error) {
|
||||
return &fakeRepoBackend{workTree: "open:" + path}, nil
|
||||
}
|
||||
|
||||
func (fakeBackend) Clone(path, remoteURL string, auth *AuthMethod, opts CloneOptions) (RepositoryBackend, error) {
|
||||
return &fakeRepoBackend{workTree: fmt.Sprintf("clone:%s:%s", path, remoteURL)}, nil
|
||||
}
|
||||
|
||||
func (r *fakeRepoBackend) WorkTree() string { return r.workTree }
|
||||
func (r *fakeRepoBackend) Config() (*Config, error) {
|
||||
return &Config{Remotes: map[string]*RemoteConfig{}, Branches: map[string]*Branch{}}, nil
|
||||
}
|
||||
|
||||
func (r *fakeRepoBackend) Head() (*Reference, error) {
|
||||
return &Reference{name: NewBranchReferenceName("main"), hash: Hash("deadbeef")}, nil
|
||||
}
|
||||
func (r *fakeRepoBackend) AddRemote(name, remoteURL string) error { return nil }
|
||||
func (r *fakeRepoBackend) SetBranchUpstream(branchName, remoteName, remoteBranch string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *fakeRepoBackend) Fetch(remoteName string, refspecs []string, auth *AuthMethod) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *fakeRepoBackend) CreateTrackingBranch(localBranchName, remoteBranchName, remoteName string) error {
|
||||
return nil
|
||||
}
|
||||
func (r *fakeRepoBackend) Checkout(ref ReferenceName) error { return nil }
|
||||
func (r *fakeRepoBackend) DeleteLocalBranch(branchName string) error { return nil }
|
||||
func (r *fakeRepoBackend) DeleteRemoteBranch(remoteName, remoteBranch string, auth *AuthMethod) error {
|
||||
return nil
|
||||
}
|
||||
func (r *fakeRepoBackend) ListReferences(prefixes ...string) ([]*Reference, error) { return nil, nil }
|
||||
func (r *fakeRepoBackend) PushToAgitFlowPR(remoteName, head, base, topic string, pushOptions map[string]string, auth *AuthMethod) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCanSwitchBackends(t *testing.T) {
|
||||
setBackendForTesting(t, fakeBackend{})
|
||||
|
||||
repo, err := RepoFromPath("demo")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "open:demo", repo.WorkTree())
|
||||
require.Equal(t, "fake", CurrentBackendName())
|
||||
|
||||
cloned, err := Clone("target", "https://example.com/repo.git", nil, 1, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "clone:target:https://example.com/repo.git", cloned.WorkTree())
|
||||
}
|
||||
|
||||
func TestRegisteredBackendsContainsCLI(t *testing.T) {
|
||||
require.Contains(t, RegisteredBackends(), "cli")
|
||||
}
|
||||
+74
-143
@@ -8,73 +8,31 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
git_config "github.com/go-git/go-git/v5/config"
|
||||
git_plumbing "github.com/go-git/go-git/v5/plumbing"
|
||||
git_transport "github.com/go-git/go-git/v5/plumbing/transport"
|
||||
)
|
||||
|
||||
// TeaCreateBranch creates a new branch in the repo, tracking from another branch.
|
||||
func (r TeaRepo) TeaCreateBranch(localBranchName, remoteBranchName, remoteName string) error {
|
||||
// save in .git/config to assign remote for future pulls
|
||||
localBranchRefName := git_plumbing.NewBranchReferenceName(localBranchName)
|
||||
err := r.CreateBranch(&git_config.Branch{
|
||||
Name: localBranchName,
|
||||
Merge: git_plumbing.NewBranchReferenceName(remoteBranchName),
|
||||
Remote: remoteName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// serialize the branch to .git/refs/heads
|
||||
remoteBranchRefName := git_plumbing.NewRemoteReferenceName(remoteName, remoteBranchName)
|
||||
remoteBranchRef, err := r.Storer.Reference(remoteBranchRefName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localHashRef := git_plumbing.NewHashReference(localBranchRefName, remoteBranchRef.Hash())
|
||||
return r.Storer.SetReference(localHashRef)
|
||||
return r.backend.CreateTrackingBranch(localBranchName, remoteBranchName, remoteName)
|
||||
}
|
||||
|
||||
// TeaCheckout checks out the given branch in the worktree.
|
||||
func (r TeaRepo) TeaCheckout(ref git_plumbing.ReferenceName) error {
|
||||
tree, err := r.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tree.Checkout(&git.CheckoutOptions{Branch: ref})
|
||||
func (r TeaRepo) TeaCheckout(ref ReferenceName) error {
|
||||
return r.backend.Checkout(ref)
|
||||
}
|
||||
|
||||
// TeaDeleteLocalBranch removes the given branch locally
|
||||
func (r TeaRepo) TeaDeleteLocalBranch(branch *git_config.Branch) error {
|
||||
err := r.DeleteBranch(branch.Name)
|
||||
// if the branch is not found that's ok, as .git/config may have no entry if
|
||||
// no remote tracking branch is configured for it (eg push without -u flag)
|
||||
if err != nil && err.Error() != "branch not found" {
|
||||
return err
|
||||
}
|
||||
return r.Storer.RemoveReference(git_plumbing.NewBranchReferenceName(branch.Name))
|
||||
// TeaDeleteLocalBranch removes the given branch locally.
|
||||
func (r TeaRepo) TeaDeleteLocalBranch(branch *Branch) error {
|
||||
return r.backend.DeleteLocalBranch(branch.Name)
|
||||
}
|
||||
|
||||
// TeaDeleteRemoteBranch removes the given branch on the given remote via git protocol
|
||||
func (r TeaRepo) TeaDeleteRemoteBranch(remoteName, remoteBranch string, auth git_transport.AuthMethod) error {
|
||||
// delete remote branch via git protocol:
|
||||
// an empty source in the refspec means remote deletion to git 🙃
|
||||
refspec := fmt.Sprintf(":%s", git_plumbing.NewBranchReferenceName(remoteBranch))
|
||||
return r.Push(&git.PushOptions{
|
||||
RemoteName: remoteName,
|
||||
RefSpecs: []git_config.RefSpec{git_config.RefSpec(refspec)},
|
||||
Prune: true,
|
||||
Auth: auth,
|
||||
})
|
||||
// TeaDeleteRemoteBranch removes the given branch on the given remote via git protocol.
|
||||
func (r TeaRepo) TeaDeleteRemoteBranch(remoteName, remoteBranch string, auth *AuthMethod) error {
|
||||
return r.backend.DeleteRemoteBranch(remoteName, remoteBranch, auth)
|
||||
}
|
||||
|
||||
// TeaFindBranchBySha returns a branch that is at the the given SHA and syncs to the
|
||||
// given remote repo.
|
||||
func (r TeaRepo) TeaFindBranchBySha(sha, repoURL string) (b *git_config.Branch, err error) {
|
||||
// find remote matching our repoURL
|
||||
func (r TeaRepo) TeaFindBranchBySha(sha, repoURL string) (b *Branch, err error) {
|
||||
remote, err := r.GetRemote(repoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -84,41 +42,26 @@ func (r TeaRepo) TeaFindBranchBySha(sha, repoURL string) (b *git_config.Branch,
|
||||
}
|
||||
remoteName := remote.Config().Name
|
||||
|
||||
// check if the given remote has our branch (.git/refs/remotes/<remoteName>/*)
|
||||
iter, err := r.References()
|
||||
refs, err := r.backend.ListReferences("refs/heads", "refs/remotes/"+remoteName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer iter.Close()
|
||||
var remoteRefName git_plumbing.ReferenceName
|
||||
var localRefName git_plumbing.ReferenceName
|
||||
err = iter.ForEach(func(ref *git_plumbing.Reference) error {
|
||||
if ref.Name().IsRemote() {
|
||||
name := ref.Name().Short()
|
||||
if ref.Hash().String() == sha && strings.HasPrefix(name, remoteName) {
|
||||
remoteRefName = ref.Name()
|
||||
}
|
||||
}
|
||||
|
||||
var remoteRefName ReferenceName
|
||||
var localRefName ReferenceName
|
||||
for _, ref := range refs {
|
||||
if ref.Name().IsRemote() && ref.Hash().String() == sha {
|
||||
remoteRefName = ref.Name()
|
||||
}
|
||||
if ref.Name().IsBranch() && ref.Hash().String() == sha {
|
||||
localRefName = ref.Name()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if remoteRefName == "" || localRefName == "" {
|
||||
// no remote tracking branch found, so a potential local branch
|
||||
// can't be a match either
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b = &git_config.Branch{
|
||||
Remote: remoteName,
|
||||
Name: localRefName.Short(),
|
||||
Merge: localRefName,
|
||||
}
|
||||
b = &Branch{Remote: remoteName, Name: localRefName.Short(), Merge: localRefName}
|
||||
return b, b.Validate()
|
||||
}
|
||||
|
||||
@@ -126,8 +69,7 @@ func (r TeaRepo) TeaFindBranchBySha(sha, repoURL string) (b *git_config.Branch,
|
||||
// remote names and syncs to the given remote repo. This method is less precise
|
||||
// than TeaFindBranchBySha(), but may be desirable if local and remote branch
|
||||
// have diverged.
|
||||
func (r TeaRepo) TeaFindBranchByName(branchName, repoURL string) (b *git_config.Branch, err error) {
|
||||
// find remote matching our repoURL
|
||||
func (r TeaRepo) TeaFindBranchByName(branchName, repoURL string) (b *Branch, err error) {
|
||||
remote, err := r.GetRemote(repoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -137,45 +79,35 @@ func (r TeaRepo) TeaFindBranchByName(branchName, repoURL string) (b *git_config.
|
||||
}
|
||||
remoteName := remote.Config().Name
|
||||
|
||||
// check if the given remote has our branch (.git/refs/remotes/<remoteName>/*)
|
||||
iter, err := r.References()
|
||||
refs, err := r.backend.ListReferences("refs/heads", "refs/remotes/"+remoteName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer iter.Close()
|
||||
var remoteRefName git_plumbing.ReferenceName
|
||||
var localRefName git_plumbing.ReferenceName
|
||||
|
||||
var remoteRefName ReferenceName
|
||||
var localRefName ReferenceName
|
||||
remoteSearchingName := fmt.Sprintf("%s/%s", remoteName, branchName)
|
||||
err = iter.ForEach(func(ref *git_plumbing.Reference) error {
|
||||
for _, ref := range refs {
|
||||
if ref.Name().IsRemote() && ref.Name().Short() == remoteSearchingName {
|
||||
remoteRefName = ref.Name()
|
||||
}
|
||||
n := ref.Name()
|
||||
if n.IsBranch() && n.Short() == branchName {
|
||||
localRefName = n
|
||||
if ref.Name().IsBranch() && ref.Name().Short() == branchName {
|
||||
localRefName = ref.Name()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if remoteRefName == "" || localRefName == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b = &git_config.Branch{
|
||||
Remote: remoteName,
|
||||
Name: localRefName.Short(),
|
||||
Merge: localRefName,
|
||||
}
|
||||
b = &Branch{Remote: remoteName, Name: localRefName.Short(), Merge: localRefName}
|
||||
return b, b.Validate()
|
||||
}
|
||||
|
||||
// TeaFindBranchRemote gives the first remote that has a branch with the same name or sha,
|
||||
// depending on what is passed in.
|
||||
// This function is needed, as git does not always define branches in .git/config with remote entries.
|
||||
// Priority order is: first match of sha and branch -> first match of branch -> first match of sha
|
||||
func (r TeaRepo) TeaFindBranchRemote(branchName, hash string) (*git.Remote, error) {
|
||||
// Priority order is: first match of sha and branch -> first match of branch -> first match of sha.
|
||||
func (r TeaRepo) TeaFindBranchRemote(branchName, hash string) (*Remote, error) {
|
||||
remotes, err := r.Remotes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -188,55 +120,53 @@ func (r TeaRepo) TeaFindBranchRemote(branchName, hash string) (*git.Remote, erro
|
||||
return remotes[0], nil
|
||||
}
|
||||
|
||||
// check if the given remote has our branch (.git/refs/remotes/<remoteName>/*)
|
||||
iter, err := r.References()
|
||||
refs, err := r.backend.ListReferences("refs/remotes")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
var shaMatch *git.Remote
|
||||
var branchMatch *git.Remote
|
||||
var fullMatch *git.Remote
|
||||
if err := iter.ForEach(func(ref *git_plumbing.Reference) error {
|
||||
if ref.Name().IsRemote() {
|
||||
names := strings.SplitN(ref.Name().Short(), "/", 2)
|
||||
remote := names[0]
|
||||
branch := names[1]
|
||||
if branchMatch == nil && branchName != "" && branchName == branch {
|
||||
if branchMatch, err = r.Remote(remote); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if shaMatch == nil && hash != "" && hash == ref.Hash().String() {
|
||||
if shaMatch, err = r.Remote(remote); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if fullMatch == nil && branchName != "" && branchName == branch && hash != "" && hash == ref.Hash().String() {
|
||||
if fullMatch, err = r.Remote(remote); err != nil {
|
||||
return err
|
||||
}
|
||||
// stop asap you have a full match
|
||||
return nil
|
||||
}
|
||||
remoteByName := make(map[string]*Remote, len(remotes))
|
||||
for _, remote := range remotes {
|
||||
remoteByName[remote.Config().Name] = remote
|
||||
}
|
||||
|
||||
var shaMatch *Remote
|
||||
var branchMatch *Remote
|
||||
var fullMatch *Remote
|
||||
for _, ref := range refs {
|
||||
remoteName, remoteBranch, ok := splitRemoteRef(ref.Name())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
remote := remoteByName[remoteName]
|
||||
if remote == nil {
|
||||
continue
|
||||
}
|
||||
if branchMatch == nil && branchName != "" && branchName == remoteBranch {
|
||||
branchMatch = remote
|
||||
}
|
||||
if shaMatch == nil && hash != "" && hash == ref.Hash().String() {
|
||||
shaMatch = remote
|
||||
}
|
||||
if fullMatch == nil && branchName != "" && branchName == remoteBranch && hash != "" && hash == ref.Hash().String() {
|
||||
fullMatch = remote
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fullMatch != nil {
|
||||
switch {
|
||||
case fullMatch != nil:
|
||||
return fullMatch, nil
|
||||
} else if branchMatch != nil {
|
||||
case branchMatch != nil:
|
||||
return branchMatch, nil
|
||||
} else if shaMatch != nil {
|
||||
case shaMatch != nil:
|
||||
return shaMatch, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TeaGetCurrentBranchNameAndSHA return the name and sha of the branch witch is currently active
|
||||
// TeaGetCurrentBranchNameAndSHA return the name and sha of the branch witch is currently active.
|
||||
func (r TeaRepo) TeaGetCurrentBranchNameAndSHA() (string, string, error) {
|
||||
localHead, err := r.Head()
|
||||
if err != nil {
|
||||
@@ -251,13 +181,11 @@ func (r TeaRepo) TeaGetCurrentBranchNameAndSHA() (string, string, error) {
|
||||
}
|
||||
|
||||
// PushToCreatAgitFlowPR pushes the given head to the refs/for/<base>/<topic> ref on the remote to create an agit flow PR.
|
||||
func (r TeaRepo) PushToCreatAgitFlowPR(remoteName, head, base, topic, title, description string, auth git_transport.AuthMethod) error {
|
||||
func (r TeaRepo) PushToCreatAgitFlowPR(remoteName, head, base, topic, title, description string, auth *AuthMethod) error {
|
||||
if !strings.HasPrefix(head, "refs/") {
|
||||
head = "refs/heads/" + head
|
||||
}
|
||||
|
||||
ref := fmt.Sprintf("%s:refs/for/%s/%s", head, base, topic)
|
||||
|
||||
pushOptions := make(map[string]string)
|
||||
if len(title) > 0 {
|
||||
pushOptions["title"] = b64Encode(title)
|
||||
@@ -265,15 +193,18 @@ func (r TeaRepo) PushToCreatAgitFlowPR(remoteName, head, base, topic, title, des
|
||||
if len(description) > 0 {
|
||||
pushOptions["description"] = b64Encode(description)
|
||||
}
|
||||
return r.backend.PushToAgitFlowPR(remoteName, head, base, topic, pushOptions, auth)
|
||||
}
|
||||
|
||||
opts := &git.PushOptions{
|
||||
RemoteName: remoteName,
|
||||
RefSpecs: []git_config.RefSpec{git_config.RefSpec(ref)},
|
||||
Options: pushOptions,
|
||||
Auth: auth,
|
||||
func splitRemoteRef(ref ReferenceName) (remoteName, branchName string, ok bool) {
|
||||
if !ref.IsRemote() {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
return r.Push(opts)
|
||||
parts := strings.SplitN(ref.Short(), "/", 2)
|
||||
if len(parts) != 2 {
|
||||
return "", "", false
|
||||
}
|
||||
return parts[0], parts[1], true
|
||||
}
|
||||
|
||||
// b64Encode implements base64 encode for string if necessary.
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type cliBackend struct{}
|
||||
|
||||
type cliRepository struct {
|
||||
workTree string
|
||||
}
|
||||
|
||||
func (cliBackend) Name() string {
|
||||
return "cli"
|
||||
}
|
||||
|
||||
func (b cliBackend) Open(path string) (RepositoryBackend, error) {
|
||||
if path == "" {
|
||||
path = "."
|
||||
}
|
||||
|
||||
out, err := runGitCommand(path, nil, nil, "rev-parse", "--show-toplevel")
|
||||
if err != nil {
|
||||
return nil, classifyRepoError(err)
|
||||
}
|
||||
|
||||
return &cliRepository{workTree: strings.TrimSpace(out)}, nil
|
||||
}
|
||||
|
||||
func (b cliBackend) Clone(path, remoteURL string, auth *AuthMethod, opts CloneOptions) (RepositoryBackend, error) {
|
||||
extraConfigs := make([]string, 0, 1)
|
||||
if opts.Insecure {
|
||||
extraConfigs = append(extraConfigs, "http.sslVerify=false")
|
||||
}
|
||||
|
||||
args := []string{"clone"}
|
||||
if opts.Depth > 0 {
|
||||
args = append(args, "--depth", strconv.Itoa(opts.Depth))
|
||||
}
|
||||
args = append(args, remoteURL, path)
|
||||
|
||||
if _, err := runGitCommand("", auth, extraConfigs, args...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.Open(path)
|
||||
}
|
||||
|
||||
func (r *cliRepository) WorkTree() string {
|
||||
return r.workTree
|
||||
}
|
||||
|
||||
func (r *cliRepository) Config() (*Config, error) {
|
||||
cfg := &Config{
|
||||
Remotes: map[string]*RemoteConfig{},
|
||||
Branches: map[string]*Branch{},
|
||||
}
|
||||
|
||||
remoteOut, err := r.git(nil, nil, "remote")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, remoteName := range strings.Fields(remoteOut) {
|
||||
urlOut, err := r.git(nil, nil, "config", "--get-all", "remote."+remoteName+".url")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.Remotes[remoteName] = &RemoteConfig{Name: remoteName, URLs: splitNonEmptyLines(urlOut)}
|
||||
}
|
||||
|
||||
branchOut, err := r.git(nil, nil, "config", "--get-regexp", `^branch\..*\.(remote|merge)$`)
|
||||
if err != nil {
|
||||
var gitErr *gitCommandError
|
||||
if !errors.As(err, &gitErr) {
|
||||
return nil, err
|
||||
}
|
||||
if strings.TrimSpace(gitErr.stderr) != "" && !strings.Contains(gitErr.stderr, "No such section or key") {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
for _, line := range splitNonEmptyLines(branchOut) {
|
||||
parts := strings.SplitN(line, " ", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
branchName, field, ok := parseBranchConfigKey(parts[0])
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
branch := cfg.Branches[branchName]
|
||||
if branch == nil {
|
||||
branch = &Branch{Name: branchName}
|
||||
cfg.Branches[branchName] = branch
|
||||
}
|
||||
switch field {
|
||||
case "remote":
|
||||
branch.Remote = parts[1]
|
||||
case "merge":
|
||||
branch.Merge = ReferenceName(parts[1])
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (r *cliRepository) Head() (*Reference, error) {
|
||||
hashOut, err := r.git(nil, nil, "rev-parse", "HEAD")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hash := Hash(strings.TrimSpace(hashOut))
|
||||
|
||||
if refOut, err := r.git(nil, nil, "symbolic-ref", "-q", "HEAD"); err == nil {
|
||||
return &Reference{name: ReferenceName(strings.TrimSpace(refOut)), hash: hash}, nil
|
||||
}
|
||||
if tagOut, err := r.git(nil, nil, "describe", "--exact-match", "--tags", "HEAD"); err == nil {
|
||||
return &Reference{name: ReferenceName("refs/tags/" + strings.TrimSpace(tagOut)), hash: hash}, nil
|
||||
}
|
||||
return &Reference{name: ReferenceName("HEAD"), hash: hash}, nil
|
||||
}
|
||||
|
||||
func (r *cliRepository) AddRemote(name, remoteURL string) error {
|
||||
_, err := r.git(nil, nil, "remote", "add", name, remoteURL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *cliRepository) SetBranchUpstream(branchName, remoteName, remoteBranch string) error {
|
||||
mergeRef := NewBranchReferenceName(remoteBranch).String()
|
||||
if _, err := r.git(nil, nil, "config", "branch."+branchName+".remote", remoteName); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := r.git(nil, nil, "config", "branch."+branchName+".merge", mergeRef)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *cliRepository) Fetch(remoteName string, refspecs []string, auth *AuthMethod) error {
|
||||
args := []string{"fetch", remoteName}
|
||||
args = append(args, refspecs...)
|
||||
_, err := r.git(auth, nil, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *cliRepository) CreateTrackingBranch(localBranchName, remoteBranchName, remoteName string) error {
|
||||
_, err := r.git(nil, nil, "branch", "--track", localBranchName, fmt.Sprintf("%s/%s", remoteName, remoteBranchName))
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if gitErr, ok := err.(*gitCommandError); ok && strings.Contains(gitErr.stderr, "already exists") {
|
||||
return ErrBranchExists
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *cliRepository) Checkout(ref ReferenceName) error {
|
||||
_, err := r.git(nil, nil, "checkout", ref.String())
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *cliRepository) DeleteLocalBranch(branchName string) error {
|
||||
_, err := r.git(nil, nil, "branch", "-D", branchName)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if gitErr, ok := err.(*gitCommandError); ok {
|
||||
stderr := strings.ToLower(gitErr.stderr)
|
||||
if strings.Contains(stderr, "not found") || strings.Contains(stderr, "not exist") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *cliRepository) DeleteRemoteBranch(remoteName, remoteBranch string, auth *AuthMethod) error {
|
||||
_, err := r.git(auth, nil, "push", "--delete", remoteName, remoteBranch)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *cliRepository) ListReferences(prefixes ...string) ([]*Reference, error) {
|
||||
args := []string{"for-each-ref", "--format=%(objectname)%09%(refname)"}
|
||||
args = append(args, prefixes...)
|
||||
out, err := r.git(nil, nil, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refs := make([]*Reference, 0)
|
||||
for _, line := range splitNonEmptyLines(out) {
|
||||
parts := strings.SplitN(line, " ", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
refs = append(refs, &Reference{name: ReferenceName(parts[1]), hash: Hash(parts[0])})
|
||||
}
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
func (r *cliRepository) PushToAgitFlowPR(remoteName, head, base, topic string, pushOptions map[string]string, auth *AuthMethod) error {
|
||||
ref := fmt.Sprintf("%s:refs/for/%s/%s", head, base, topic)
|
||||
args := []string{"push", remoteName, ref}
|
||||
if len(pushOptions) > 0 {
|
||||
keys := make([]string, 0, len(pushOptions))
|
||||
for key := range pushOptions {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
args = append(args, "-o", key+"="+pushOptions[key])
|
||||
}
|
||||
}
|
||||
_, err := r.git(auth, nil, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *cliRepository) git(auth *AuthMethod, extraConfigs []string, args ...string) (string, error) {
|
||||
return runGitCommand(r.workTree, auth, extraConfigs, args...)
|
||||
}
|
||||
|
||||
type gitCommandError struct {
|
||||
args []string
|
||||
stderr string
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *gitCommandError) Error() string {
|
||||
stderr := strings.TrimSpace(e.stderr)
|
||||
if stderr == "" {
|
||||
return fmt.Sprintf("git %s: %v", strings.Join(e.args, " "), e.err)
|
||||
}
|
||||
return fmt.Sprintf("git %s: %s", strings.Join(e.args, " "), stderr)
|
||||
}
|
||||
|
||||
func (e *gitCommandError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func runGitCommand(dir string, auth *AuthMethod, extraConfigs []string, args ...string) (string, error) {
|
||||
authConfigs, authEnv, cleanup, err := prepareCLIAuth(auth)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
fullArgs := make([]string, 0, (len(extraConfigs)+len(authConfigs))*2+len(args))
|
||||
for _, cfg := range extraConfigs {
|
||||
fullArgs = append(fullArgs, "-c", cfg)
|
||||
}
|
||||
for _, cfg := range authConfigs {
|
||||
fullArgs = append(fullArgs, "-c", cfg)
|
||||
}
|
||||
fullArgs = append(fullArgs, args...)
|
||||
|
||||
cmd := exec.Command("git", fullArgs...)
|
||||
if dir != "" {
|
||||
cmd.Dir = dir
|
||||
}
|
||||
cmd.Env = append(os.Environ(), authEnv...)
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", &gitCommandError{args: fullArgs, stderr: stderr.String(), err: err}
|
||||
}
|
||||
return stdout.String(), nil
|
||||
}
|
||||
|
||||
func prepareCLIAuth(auth *AuthMethod) ([]string, []string, func(), error) {
|
||||
if auth == nil {
|
||||
return nil, nil, func() {}, nil
|
||||
}
|
||||
|
||||
configs := make([]string, 0, 1)
|
||||
env := make([]string, 0, 4)
|
||||
cleanup := func() {}
|
||||
|
||||
switch auth.Scheme {
|
||||
case "http", "https":
|
||||
if auth.Username != "" || auth.Password != "" {
|
||||
header := "Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth.Username+":"+auth.Password))
|
||||
configs = append(configs, "http.extraHeader="+header)
|
||||
}
|
||||
case "ssh":
|
||||
sshCommand := "ssh"
|
||||
if auth.KeyFile != "" {
|
||||
sshCommand += " -i " + shellQuote(auth.KeyFile) + " -o IdentitiesOnly=yes"
|
||||
}
|
||||
env = append(env, "GIT_SSH_COMMAND="+sshCommand)
|
||||
if auth.KeyPassphrase != "" {
|
||||
askPassPath, err := writeAskPassScript(auth.KeyPassphrase)
|
||||
if err != nil {
|
||||
return nil, nil, cleanup, err
|
||||
}
|
||||
cleanup = func() { _ = os.Remove(askPassPath) }
|
||||
env = append(env,
|
||||
"SSH_ASKPASS="+askPassPath,
|
||||
"SSH_ASKPASS_REQUIRE=force",
|
||||
"DISPLAY=tea",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return configs, env, cleanup, nil
|
||||
}
|
||||
|
||||
func writeAskPassScript(passphrase string) (string, error) {
|
||||
f, err := os.CreateTemp("", "tea-ssh-askpass-*")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
content := "#!/bin/sh\nprintf '%s\\n' " + shellQuote(passphrase) + "\n"
|
||||
if _, err := f.WriteString(content); err != nil {
|
||||
_ = os.Remove(f.Name())
|
||||
return "", err
|
||||
}
|
||||
if err := f.Chmod(0o700); err != nil {
|
||||
_ = os.Remove(f.Name())
|
||||
return "", err
|
||||
}
|
||||
return f.Name(), nil
|
||||
}
|
||||
|
||||
func classifyRepoError(err error) error {
|
||||
var gitErr *gitCommandError
|
||||
if errors.As(err, &gitErr) {
|
||||
msg := strings.ToLower(gitErr.stderr)
|
||||
if strings.Contains(msg, "not a git repository") || strings.Contains(msg, "cannot change to") {
|
||||
return ErrRepositoryNotExists
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func parseBranchConfigKey(key string) (branchName, field string, ok bool) {
|
||||
const prefix = "branch."
|
||||
if !strings.HasPrefix(key, prefix) {
|
||||
return "", "", false
|
||||
}
|
||||
trimmed := strings.TrimPrefix(key, prefix)
|
||||
switch {
|
||||
case strings.HasSuffix(trimmed, ".remote"):
|
||||
return strings.TrimSuffix(trimmed, ".remote"), "remote", true
|
||||
case strings.HasSuffix(trimmed, ".merge"):
|
||||
return strings.TrimSuffix(trimmed, ".merge"), "merge", true
|
||||
default:
|
||||
return "", "", false
|
||||
}
|
||||
}
|
||||
|
||||
func splitNonEmptyLines(s string) []string {
|
||||
lines := strings.Split(strings.TrimSpace(s), "\n")
|
||||
out := make([]string, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
out = append(out, line)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
// Clone clones a repository using the active backend.
|
||||
func Clone(path, remoteURL string, auth *AuthMethod, depth int, insecure bool) (*TeaRepo, error) {
|
||||
backend, err := currentBackend().Clone(path, remoteURL, auth, CloneOptions{Depth: depth, Insecure: insecure})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newTeaRepo(backend), nil
|
||||
}
|
||||
|
||||
// AddRemote adds a new remote to the repository.
|
||||
func (r TeaRepo) AddRemote(name, remoteURL string) error {
|
||||
return r.backend.AddRemote(name, remoteURL)
|
||||
}
|
||||
|
||||
// SetBranchUpstream configures the branch's upstream remote.
|
||||
func (r TeaRepo) SetBranchUpstream(branchName, remoteName, remoteBranch string) error {
|
||||
return r.backend.SetBranchUpstream(branchName, remoteName, remoteBranch)
|
||||
}
|
||||
|
||||
// Fetch fetches updates from the named remote.
|
||||
func (r TeaRepo) Fetch(remoteName string, refspecs []string, auth *AuthMethod) error {
|
||||
return r.backend.Fetch(remoteName, refspecs, auth)
|
||||
}
|
||||
+11
-18
@@ -6,14 +6,11 @@ package git
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
git_config "github.com/go-git/go-git/v5/config"
|
||||
)
|
||||
|
||||
// GetRemote tries to match a Remote of the repo via the given URL.
|
||||
// Matching is based on the normalized URL, accepting different protocols.
|
||||
func (r TeaRepo) GetRemote(remoteURL string) (*git.Remote, error) {
|
||||
func (r TeaRepo) GetRemote(remoteURL string) (*Remote, error) {
|
||||
repoURL, err := ParseURL(remoteURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -23,14 +20,14 @@ func (r TeaRepo) GetRemote(remoteURL string) (*git.Remote, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range remotes {
|
||||
for _, u := range r.Config().URLs {
|
||||
remoteURL, err := ParseURL(u)
|
||||
for _, remote := range remotes {
|
||||
for _, u := range remote.Config().URLs {
|
||||
parsedRemoteURL, err := ParseURL(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if remoteURL.Host == repoURL.Host && remoteURL.Path == repoURL.Path {
|
||||
return r, nil
|
||||
if parsedRemoteURL.Host == repoURL.Host && parsedRemoteURL.Path == repoURL.Path {
|
||||
return remote, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,27 +38,23 @@ func (r TeaRepo) GetRemote(remoteURL string) (*git.Remote, error) {
|
||||
// GetOrCreateRemote tries to match a Remote of the repo via the given URL.
|
||||
// If no match is found, a new Remote with `newRemoteName` is created.
|
||||
// Matching is based on the normalized URL, accepting different protocols.
|
||||
func (r TeaRepo) GetOrCreateRemote(remoteURL, newRemoteName string) (*git.Remote, error) {
|
||||
func (r TeaRepo) GetOrCreateRemote(remoteURL, newRemoteName string) (*Remote, error) {
|
||||
localRemote, err := r.GetRemote(remoteURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if no match found, create a new remote
|
||||
if localRemote == nil {
|
||||
localRemote, err = r.CreateRemote(&git_config.RemoteConfig{
|
||||
Name: newRemoteName,
|
||||
URLs: []string{remoteURL},
|
||||
})
|
||||
if err != nil {
|
||||
if err := r.AddRemote(newRemoteName, remoteURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.Remote(newRemoteName)
|
||||
}
|
||||
|
||||
return localRemote, nil
|
||||
}
|
||||
|
||||
// TeaRemoteURL returns the first url entry for the given remote name
|
||||
// TeaRemoteURL returns the first url entry for the given remote name.
|
||||
func (r TeaRepo) TeaRemoteURL(name string) (auth *url.URL, err error) {
|
||||
remote, err := r.Remote(name)
|
||||
if err != nil {
|
||||
@@ -71,5 +64,5 @@ func (r TeaRepo) TeaRemoteURL(name string) (auth *url.URL, err error) {
|
||||
if len(urls) == 0 {
|
||||
return nil, fmt.Errorf("remote %s has no URL configured", name)
|
||||
}
|
||||
return ParseURL(remote.Config().URLs[0])
|
||||
return ParseURL(urls[0])
|
||||
}
|
||||
|
||||
+62
-15
@@ -4,14 +4,18 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// TeaRepo is a go-git Repository, with an extended high level interface.
|
||||
// TeaRepo wraps a local git repository behind a swappable backend.
|
||||
type TeaRepo struct {
|
||||
*git.Repository
|
||||
backend RepositoryBackend
|
||||
}
|
||||
|
||||
func newTeaRepo(backend RepositoryBackend) *TeaRepo {
|
||||
return &TeaRepo{backend: backend}
|
||||
}
|
||||
|
||||
// RepoForWorkdir tries to open the git repository in the local directory
|
||||
@@ -20,28 +24,71 @@ func RepoForWorkdir() (*TeaRepo, error) {
|
||||
return RepoFromPath("")
|
||||
}
|
||||
|
||||
// RepoFromPath tries to open the git repository by path
|
||||
// RepoFromPath tries to open the git repository by path.
|
||||
func RepoFromPath(path string) (*TeaRepo, error) {
|
||||
if len(path) == 0 {
|
||||
path = "./"
|
||||
backend, err := currentBackend().Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repo, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{
|
||||
DetectDotGit: true,
|
||||
EnableDotGitCommonDir: true, // Enable commondir support for worktrees
|
||||
})
|
||||
return newTeaRepo(backend), nil
|
||||
}
|
||||
|
||||
// WorkTree returns the repository work tree path.
|
||||
func (r TeaRepo) WorkTree() string {
|
||||
return r.backend.WorkTree()
|
||||
}
|
||||
|
||||
// Config returns the repository config values tea needs.
|
||||
func (r TeaRepo) Config() (*Config, error) {
|
||||
return r.backend.Config()
|
||||
}
|
||||
|
||||
// Remote returns the configured remote by name.
|
||||
func (r TeaRepo) Remote(remoteName string) (*Remote, error) {
|
||||
cfg, err := r.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remoteCfg, ok := cfg.Remotes[remoteName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("remote %s not found", remoteName)
|
||||
}
|
||||
return &Remote{repo: &r, config: remoteCfg}, nil
|
||||
}
|
||||
|
||||
// Remotes returns all configured remotes sorted by name.
|
||||
func (r TeaRepo) Remotes() ([]*Remote, error) {
|
||||
cfg, err := r.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TeaRepo{repo}, nil
|
||||
remoteNames := make([]string, 0, len(cfg.Remotes))
|
||||
for name := range cfg.Remotes {
|
||||
remoteNames = append(remoteNames, name)
|
||||
}
|
||||
sort.Strings(remoteNames)
|
||||
|
||||
remotes := make([]*Remote, 0, len(remoteNames))
|
||||
for _, name := range remoteNames {
|
||||
remotes = append(remotes, &Remote{repo: &r, config: cfg.Remotes[name]})
|
||||
}
|
||||
return remotes, nil
|
||||
}
|
||||
|
||||
// RemoteURL returns the URL of the given remote
|
||||
// Head returns the currently checked out ref.
|
||||
func (r TeaRepo) Head() (*Reference, error) {
|
||||
return r.backend.Head()
|
||||
}
|
||||
|
||||
// RemoteURL returns the URL of the given remote.
|
||||
func (r TeaRepo) RemoteURL(remoteName string) (*url.URL, error) {
|
||||
remote, err := r.Remote(remoteName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return url.Parse(remote.Config().URLs[0])
|
||||
if len(remote.Config().URLs) == 0 {
|
||||
return nil, fmt.Errorf("remote %s has no URL configured", remoteName)
|
||||
}
|
||||
return ParseURL(remote.Config().URLs[0])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func runGit(t *testing.T, dir string, args ...string) string {
|
||||
t.Helper()
|
||||
cmd := exec.Command("git", args...)
|
||||
if dir != "" {
|
||||
cmd.Dir = dir
|
||||
}
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoErrorf(t, err, "git %v failed: %s", args, out)
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func tryGit(dir string, args ...string) error {
|
||||
cmd := exec.Command("git", args...)
|
||||
if dir != "" {
|
||||
cmd.Dir = dir
|
||||
}
|
||||
_, err := cmd.CombinedOutput()
|
||||
return err
|
||||
}
|
||||
|
||||
func TestRepoFromPathSupportsWorktrees(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
mainRepoPath := filepath.Join(tmpDir, "main-repo")
|
||||
worktreePath := filepath.Join(tmpDir, "worktree")
|
||||
|
||||
runGit(t, "", "init", mainRepoPath)
|
||||
runGit(t, mainRepoPath, "config", "user.email", "test@example.com")
|
||||
runGit(t, mainRepoPath, "config", "user.name", "Test User")
|
||||
runGit(t, mainRepoPath, "remote", "add", "origin", "https://gitea.com/owner/repo.git")
|
||||
|
||||
readmePath := filepath.Join(mainRepoPath, "README.md")
|
||||
require.NoError(t, os.WriteFile(readmePath, []byte("# Test Repo\n"), 0o644))
|
||||
runGit(t, mainRepoPath, "add", "README.md")
|
||||
runGit(t, mainRepoPath, "commit", "-m", "Initial commit")
|
||||
runGit(t, mainRepoPath, "worktree", "add", worktreePath, "-b", "test-branch")
|
||||
|
||||
repo, err := RepoFromPath(worktreePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
config, err := repo.Config()
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, config.Remotes, "origin")
|
||||
require.Equal(t, "https://gitea.com/owner/repo.git", config.Remotes["origin"].URLs[0])
|
||||
|
||||
head, err := repo.Head()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test-branch", head.Name().Short())
|
||||
}
|
||||
|
||||
func TestRepoFromPathSupportsSHA256Repos(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
repoPath := filepath.Join(tmpDir, "sha256-repo")
|
||||
|
||||
if err := tryGit("", "init", "--object-format=sha256", repoPath); err != nil {
|
||||
t.Skip("git does not support sha256 object format in this environment")
|
||||
}
|
||||
|
||||
runGit(t, repoPath, "config", "user.email", "test@example.com")
|
||||
runGit(t, repoPath, "config", "user.name", "Test User")
|
||||
runGit(t, repoPath, "remote", "add", "origin", "https://gitea.com/owner/repo.git")
|
||||
|
||||
readmePath := filepath.Join(repoPath, "README.md")
|
||||
require.NoError(t, os.WriteFile(readmePath, []byte("sha256\n"), 0o644))
|
||||
runGit(t, repoPath, "add", "README.md")
|
||||
runGit(t, repoPath, "commit", "-m", "Initial commit")
|
||||
|
||||
repo, err := RepoFromPath(repoPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
branch, sha, err := repo.TeaGetCurrentBranchNameAndSHA()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, branch)
|
||||
require.Len(t, sha, 64)
|
||||
|
||||
config, err := repo.Config()
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, config.Remotes, "origin")
|
||||
}
|
||||
|
||||
func TestTeaFindBranchByShaAndName(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
remotePath := filepath.Join(tmpDir, "remote.git")
|
||||
localPath := filepath.Join(tmpDir, "local")
|
||||
|
||||
runGit(t, "", "init", "--bare", remotePath)
|
||||
runGit(t, "", "clone", remotePath, localPath)
|
||||
runGit(t, localPath, "config", "user.email", "test@example.com")
|
||||
runGit(t, localPath, "config", "user.name", "Test User")
|
||||
|
||||
filePath := filepath.Join(localPath, "README.md")
|
||||
require.NoError(t, os.WriteFile(filePath, []byte("main\n"), 0o644))
|
||||
runGit(t, localPath, "add", "README.md")
|
||||
runGit(t, localPath, "commit", "-m", "Initial commit")
|
||||
runGit(t, localPath, "branch", "-M", "main")
|
||||
runGit(t, localPath, "push", "-u", "origin", "main")
|
||||
runGit(t, localPath, "checkout", "-b", "feature/demo")
|
||||
|
||||
require.NoError(t, os.WriteFile(filePath, []byte("feature\n"), 0o644))
|
||||
runGit(t, localPath, "commit", "-am", "Feature commit")
|
||||
runGit(t, localPath, "push", "-u", "origin", "feature/demo")
|
||||
|
||||
repo, err := RepoFromPath(localPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
sha := strings.TrimSpace(runGit(t, localPath, "rev-parse", "HEAD"))
|
||||
branchBySha, err := repo.TeaFindBranchBySha(sha, remotePath)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, branchBySha)
|
||||
require.Equal(t, "feature/demo", branchBySha.Name)
|
||||
require.Equal(t, "origin", branchBySha.Remote)
|
||||
|
||||
branchByName, err := repo.TeaFindBranchByName("feature/demo", remotePath)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, branchByName)
|
||||
require.Equal(t, "feature/demo", branchByName.Name)
|
||||
|
||||
remote, err := repo.TeaFindBranchRemote("feature/demo", sha)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, remote)
|
||||
require.Equal(t, "origin", remote.Config().Name)
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrRepositoryNotExists indicates the requested path is not inside a git repository.
|
||||
ErrRepositoryNotExists = errors.New("repository does not exist")
|
||||
// ErrBranchExists indicates the requested branch already exists locally.
|
||||
ErrBranchExists = errors.New("branch already exists")
|
||||
)
|
||||
|
||||
// AuthMethod carries backend-agnostic authentication information for git operations.
|
||||
type AuthMethod struct {
|
||||
Scheme string
|
||||
Username string
|
||||
Password string
|
||||
KeyFile string
|
||||
KeyPassphrase string
|
||||
}
|
||||
|
||||
// CloneOptions describes repository clone behavior.
|
||||
type CloneOptions struct {
|
||||
Depth int
|
||||
Insecure bool
|
||||
}
|
||||
|
||||
// RepositoryBackend is the backend abstraction used by TeaRepo.
|
||||
type RepositoryBackend interface {
|
||||
WorkTree() string
|
||||
Config() (*Config, error)
|
||||
Head() (*Reference, error)
|
||||
AddRemote(name, remoteURL string) error
|
||||
SetBranchUpstream(branchName, remoteName, remoteBranch string) error
|
||||
Fetch(remoteName string, refspecs []string, auth *AuthMethod) error
|
||||
CreateTrackingBranch(localBranchName, remoteBranchName, remoteName string) error
|
||||
Checkout(ref ReferenceName) error
|
||||
DeleteLocalBranch(branchName string) error
|
||||
DeleteRemoteBranch(remoteName, remoteBranch string, auth *AuthMethod) error
|
||||
ListReferences(prefixes ...string) ([]*Reference, error)
|
||||
PushToAgitFlowPR(remoteName, head, base, topic string, pushOptions map[string]string, auth *AuthMethod) error
|
||||
}
|
||||
|
||||
// Backend opens and clones repositories using a concrete git implementation.
|
||||
type Backend interface {
|
||||
Name() string
|
||||
Open(path string) (RepositoryBackend, error)
|
||||
Clone(path, remoteURL string, auth *AuthMethod, opts CloneOptions) (RepositoryBackend, error)
|
||||
}
|
||||
|
||||
// Config mirrors the repository config fields tea needs.
|
||||
type Config struct {
|
||||
Remotes map[string]*RemoteConfig
|
||||
Branches map[string]*Branch
|
||||
}
|
||||
|
||||
// RemoteConfig stores remote configuration.
|
||||
type RemoteConfig struct {
|
||||
Name string
|
||||
URLs []string
|
||||
}
|
||||
|
||||
// Branch stores branch configuration.
|
||||
type Branch struct {
|
||||
Name string
|
||||
Remote string
|
||||
Merge ReferenceName
|
||||
}
|
||||
|
||||
// Validate checks whether the branch contains the fields tea needs.
|
||||
func (b *Branch) Validate() error {
|
||||
if b == nil || b.Name == "" {
|
||||
return errors.New("branch name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remote wraps a configured git remote.
|
||||
type Remote struct {
|
||||
repo *TeaRepo
|
||||
config *RemoteConfig
|
||||
}
|
||||
|
||||
// Config returns the remote configuration.
|
||||
func (r *Remote) Config() *RemoteConfig {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return r.config
|
||||
}
|
||||
|
||||
// ReferenceName identifies a git reference.
|
||||
type ReferenceName string
|
||||
|
||||
func (r ReferenceName) String() string { return string(r) }
|
||||
|
||||
// Short returns the short display name for the reference.
|
||||
func (r ReferenceName) Short() string {
|
||||
s := string(r)
|
||||
switch {
|
||||
case strings.HasPrefix(s, "refs/heads/"):
|
||||
return strings.TrimPrefix(s, "refs/heads/")
|
||||
case strings.HasPrefix(s, "refs/remotes/"):
|
||||
return strings.TrimPrefix(s, "refs/remotes/")
|
||||
case strings.HasPrefix(s, "refs/tags/"):
|
||||
return strings.TrimPrefix(s, "refs/tags/")
|
||||
default:
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// IsBranch reports whether the reference points to a local branch.
|
||||
func (r ReferenceName) IsBranch() bool {
|
||||
return strings.HasPrefix(string(r), "refs/heads/")
|
||||
}
|
||||
|
||||
// IsRemote reports whether the reference points to a remote-tracking branch.
|
||||
func (r ReferenceName) IsRemote() bool {
|
||||
return strings.HasPrefix(string(r), "refs/remotes/")
|
||||
}
|
||||
|
||||
// IsTag reports whether the reference points to a tag.
|
||||
func (r ReferenceName) IsTag() bool {
|
||||
return strings.HasPrefix(string(r), "refs/tags/")
|
||||
}
|
||||
|
||||
// Hash wraps a git object id.
|
||||
type Hash string
|
||||
|
||||
func (h Hash) String() string { return string(h) }
|
||||
|
||||
// Reference stores a resolved git ref and its hash.
|
||||
type Reference struct {
|
||||
name ReferenceName
|
||||
hash Hash
|
||||
}
|
||||
|
||||
// Name returns the reference name.
|
||||
func (r *Reference) Name() ReferenceName { return r.name }
|
||||
|
||||
// Hash returns the reference hash.
|
||||
func (r *Reference) Hash() Hash { return r.hash }
|
||||
|
||||
// NewBranchReferenceName constructs a local branch ref name.
|
||||
func NewBranchReferenceName(name string) ReferenceName {
|
||||
if strings.HasPrefix(name, "refs/") {
|
||||
return ReferenceName(name)
|
||||
}
|
||||
return ReferenceName("refs/heads/" + name)
|
||||
}
|
||||
|
||||
// NewRemoteReferenceName constructs a remote-tracking ref name.
|
||||
func NewRemoteReferenceName(remote, name string) ReferenceName {
|
||||
if strings.HasPrefix(name, "refs/") {
|
||||
return ReferenceName(name)
|
||||
}
|
||||
return ReferenceName("refs/remotes/" + remote + "/" + name)
|
||||
}
|
||||
@@ -10,10 +10,6 @@ import (
|
||||
|
||||
"gitea.dev/tea/modules/config"
|
||||
local_git "gitea.dev/tea/modules/git"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
git_config "github.com/go-git/go-git/v5/config"
|
||||
git_plumbing "github.com/go-git/go-git/v5/plumbing"
|
||||
)
|
||||
|
||||
// PullCheckout checkout current workdir to the head branch of specified pull request
|
||||
@@ -76,7 +72,7 @@ func doPRFetch(
|
||||
login *config.Login,
|
||||
pr *gitea.PullRequest,
|
||||
localRepo *local_git.TeaRepo,
|
||||
localRemote *git.Remote,
|
||||
localRemote *local_git.Remote,
|
||||
callback func(string) (string, error),
|
||||
) (string, error) {
|
||||
localRemoteName := localRemote.Config().Name
|
||||
@@ -90,25 +86,23 @@ func doPRFetch(
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fetchOpts := &git.FetchOptions{Auth: auth}
|
||||
refspecs := []string{}
|
||||
if isRemoteDeleted(pr) {
|
||||
// When the head branch is already deleted, pr.Head.Ref points to
|
||||
// `refs/pull/<idx>/head`, where the commits stay available.
|
||||
// This ref must be fetched explicitly, and does not allow pushing, so we use it
|
||||
// only in this case as fallback.
|
||||
localBranchName = fmt.Sprintf("pulls/%d", pr.Index)
|
||||
fetchOpts.RefSpecs = []git_config.RefSpec{git_config.RefSpec(fmt.Sprintf("%s:refs/remotes/%s/%s",
|
||||
refspecs = []string{fmt.Sprintf("%s:refs/remotes/%s/%s",
|
||||
pr.Head.Ref,
|
||||
localRemoteName,
|
||||
localBranchName,
|
||||
))}
|
||||
)}
|
||||
}
|
||||
fmt.Printf("Fetching PR %v (head %s:%s) from remote '%s'\n", pr.Index, url, pr.Head.Ref, localRemoteName)
|
||||
|
||||
err = localRemote.Fetch(fetchOpts)
|
||||
if err == git.NoErrAlreadyUpToDate {
|
||||
fmt.Println(err)
|
||||
} else if err != nil {
|
||||
err = localRepo.Fetch(localRemoteName, refspecs, auth)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return localBranchName, nil
|
||||
@@ -124,12 +118,12 @@ func doPRCheckout(
|
||||
) error {
|
||||
// determine the ref to checkout, depending on existence of a matching commit on a local branch
|
||||
var info string
|
||||
var checkoutRef git_plumbing.ReferenceName
|
||||
var checkoutRef local_git.ReferenceName
|
||||
|
||||
if b, _ := localRepo.TeaFindBranchBySha(pr.Head.Sha, remoteURL); b != nil {
|
||||
|
||||
// if a matching branch exists, use that
|
||||
checkoutRef = git_plumbing.NewBranchReferenceName(b.Name)
|
||||
checkoutRef = local_git.NewBranchReferenceName(b.Name)
|
||||
info = fmt.Sprintf("Found matching local branch %s, checking it out", checkoutRef.Short())
|
||||
|
||||
} else if forceCreateBranch {
|
||||
@@ -139,10 +133,10 @@ func doPRCheckout(
|
||||
if isRemoteDeleted(pr) {
|
||||
localBranchName += "-" + pr.Head.Ref
|
||||
}
|
||||
checkoutRef = git_plumbing.NewBranchReferenceName(localBranchName)
|
||||
checkoutRef = local_git.NewBranchReferenceName(localBranchName)
|
||||
if err := localRepo.TeaCreateBranch(localBranchName, localRemoteBranchName, localRemoteName); err == nil {
|
||||
info = fmt.Sprintf("Created branch '%s'\n", localBranchName)
|
||||
} else if err == git.ErrBranchExists {
|
||||
} else if err == local_git.ErrBranchExists {
|
||||
info = "There may be changes since you last checked out, run `git pull` to get them."
|
||||
} else {
|
||||
return err
|
||||
@@ -151,7 +145,7 @@ func doPRCheckout(
|
||||
} else {
|
||||
|
||||
// use the remote tracking branch
|
||||
checkoutRef = git_plumbing.NewRemoteReferenceName(localRemoteName, localRemoteBranchName)
|
||||
checkoutRef = local_git.NewRemoteReferenceName(localRemoteName, localRemoteBranchName)
|
||||
info = fmt.Sprintf(
|
||||
"Checking out remote tracking branch %s. To make changes, create a new branch:\n git checkout %s",
|
||||
checkoutRef.String(), localRemoteBranchName)
|
||||
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
|
||||
"gitea.dev/tea/modules/config"
|
||||
local_git "gitea.dev/tea/modules/git"
|
||||
git_config "github.com/go-git/go-git/v5/config"
|
||||
git_plumbing "github.com/go-git/go-git/v5/plumbing"
|
||||
)
|
||||
|
||||
// PullClean deletes local & remote feature-branches for a closed pull
|
||||
@@ -51,7 +49,7 @@ func PullClean(login *config.Login, repoOwner, repoName string, index int64, ign
|
||||
}
|
||||
|
||||
// find a branch with matching sha or name, that has a remote matching the repo url
|
||||
var branch *git_config.Branch
|
||||
var branch *local_git.Branch
|
||||
if ignoreSHA {
|
||||
branch, err = r.TeaFindBranchByName(remoteBranch, pr.Head.Repository.CloneURL)
|
||||
} else {
|
||||
@@ -77,7 +75,7 @@ call me again with the --ignore-sha flag`, remoteBranch)
|
||||
}
|
||||
if headRef.Name().Short() == branch.Name {
|
||||
fmt.Printf("Checking out '%s' to delete local branch '%s'\n", defaultBranch, branch.Name)
|
||||
ref := git_plumbing.NewBranchReferenceName(defaultBranch)
|
||||
ref := local_git.NewBranchReferenceName(defaultBranch)
|
||||
if err = r.TeaCheckout(ref); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,17 +4,12 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
|
||||
"gitea.dev/tea/modules/config"
|
||||
local_git "gitea.dev/tea/modules/git"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
git_config "github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
)
|
||||
|
||||
// RepoClone creates a local git clone in the given path, and sets up upstream remote
|
||||
@@ -46,12 +41,7 @@ func RepoClone(
|
||||
path = repoName
|
||||
}
|
||||
|
||||
repo, err := git.PlainClone(path, false, &git.CloneOptions{
|
||||
URL: originURL.String(),
|
||||
Auth: auth,
|
||||
Depth: depth,
|
||||
InsecureSkipTLS: login.Insecure,
|
||||
})
|
||||
repo, err := local_git.Clone(path, originURL.String(), auth, depth, login.Insecure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,28 +53,15 @@ func RepoClone(
|
||||
return nil, err
|
||||
}
|
||||
upstreamBranch := repoMeta.Parent.DefaultBranch
|
||||
_, err = repo.CreateRemote(&git_config.RemoteConfig{
|
||||
Name: "upstream",
|
||||
URLs: []string{upstreamURL.String()},
|
||||
})
|
||||
if err != nil {
|
||||
if err = repo.AddRemote("upstream", upstreamURL.String()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoConf, err := repo.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b, ok := repoConf.Branches[upstreamBranch]; ok {
|
||||
b.Remote = "upstream"
|
||||
b.Merge = plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", upstreamBranch))
|
||||
}
|
||||
if err = repo.SetConfig(repoConf); err != nil {
|
||||
if err = repo.SetBranchUpstream(upstreamBranch, "upstream", upstreamBranch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &local_git.TeaRepo{Repository: repo}, nil
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
func cloneURL(repo *gitea.Repository, login *config.Login) (*url.URL, error) {
|
||||
|
||||
Reference in New Issue
Block a user