Files
forge/tests/integration/git_helper_for_declarative_test.go
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

291 lines
9.1 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"context"
"fmt"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/hanzoai/git/modules/generate"
"github.com/hanzoai/git/modules/git"
"github.com/hanzoai/git/modules/git/gitcmd"
"github.com/hanzoai/git/modules/setting"
"github.com/hanzoai/git/modules/ssh"
"github.com/hanzoai/git/modules/util"
"github.com/hanzoai/git/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func withKeyFile(t *testing.T, keyname string, callback func(string)) {
tmpDir := t.TempDir()
err := os.Chmod(tmpDir, 0o700)
assert.NoError(t, err)
keyFile := filepath.Join(tmpDir, keyname)
err = ssh.GenKeyPair(keyFile, generate.SSHKeyECDSA, 0)
assert.NoError(t, err)
err = os.WriteFile(filepath.Join(tmpDir, "ssh"), []byte("#!/bin/bash\n"+
"ssh -o \"UserKnownHostsFile=/dev/null\" -o \"StrictHostKeyChecking=no\" -o \"IdentitiesOnly=yes\" -i \""+keyFile+"\" \"$@\""), 0o700)
assert.NoError(t, err)
// Setup ssh wrapper
t.Setenv("GIT_SSH", filepath.Join(tmpDir, "ssh"))
t.Setenv("GIT_SSH_COMMAND",
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i \""+keyFile+"\"")
t.Setenv("GIT_SSH_VARIANT", "ssh")
callback(keyFile)
}
func createSSHUrl(gitPath string, u *url.URL) *url.URL {
u2 := *u
u2.Scheme = "ssh"
u2.User = url.User("git")
u2.Host = net.JoinHostPort(setting.SSH.ListenHost, strconv.Itoa(setting.SSH.ListenPort))
u2.Path = gitPath
return &u2
}
// gitAddChangesDeprecated marks local changes to be ready for commit.
// Deprecated: use "git fast-import" instead for better performance and more control over the commit creation.
func gitAddChangesDeprecated(ctx context.Context, repoPath string, all bool, files ...string) error {
cmd := gitcmd.NewCommand().AddArguments("add")
if all {
cmd.AddArguments("--all")
}
cmd.AddDashesAndList(files...)
_, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
return err
}
// CommitChangesOptions the options when a commit created
type gitCommitChangesOptions struct {
Committer *git.Signature
Author *git.Signature
Message string
}
// gitCommitChangesDeprecated commits local changes with given committer, author and message.
// If author is nil, it will be the same as committer.
// Deprecated: use "git fast-import" instead for better performance and more control over the commit creation.
func gitCommitChangesDeprecated(ctx context.Context, repoPath string, opts gitCommitChangesOptions) error {
cmd := gitcmd.NewCommand()
if opts.Committer != nil {
cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name)
cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email)
}
cmd.AddArguments("commit")
if opts.Author == nil {
opts.Author = opts.Committer
}
if opts.Author != nil {
cmd.AddOptionFormat("--author='%s <%s>'", opts.Author.Name, opts.Author.Email)
}
cmd.AddOptionFormat("--message=%s", opts.Message)
_, _, err := cmd.WithDir(repoPath).RunStdString(ctx)
// No stderr but exit status 1 means nothing to commit.
if gitcmd.IsErrorExitCode(err, 1) {
return nil
}
return err
}
func onGitRun[T testing.TB](t T, callback func(T, *url.URL)) {
defer tests.PrepareTestEnv(t, 1)()
s := http.Server{
Handler: testWebRoutes,
}
u, err := url.Parse(setting.AppURL)
assert.NoError(t, err)
listener, err := net.Listen("tcp", u.Host)
i := 0
for err != nil && i <= 10 {
time.Sleep(100 * time.Millisecond)
listener, err = net.Listen("tcp", u.Host)
i++
}
assert.NoError(t, err)
u.Host = listener.Addr().String()
defer func() {
ctx, cancel := context.WithTimeout(t.Context(), 2*time.Minute)
s.Shutdown(ctx)
cancel()
}()
go s.Serve(listener)
// Started by config go ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs)
callback(t, u)
}
func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
return func(t *testing.T) {
assert.NoError(t, git.Clone(t.Context(), u.String(), dstLocalPath, git.CloneRepoOptions{}))
exist, err := util.IsExist(filepath.Join(dstLocalPath, "README.md"))
assert.NoError(t, err)
assert.True(t, exist)
}
}
func doPartialGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
return func(t *testing.T) {
assert.NoError(t, git.Clone(t.Context(), u.String(), dstLocalPath, git.CloneRepoOptions{
Filter: "blob:none",
}))
exist, err := util.IsExist(filepath.Join(dstLocalPath, "README.md"))
assert.NoError(t, err)
assert.True(t, exist)
}
}
func doGitCloneFail(u *url.URL) func(*testing.T) {
return func(t *testing.T) {
tmpDir := t.TempDir()
assert.Error(t, git.Clone(t.Context(), u.String(), tmpDir, git.CloneRepoOptions{}))
exist, err := util.IsExist(filepath.Join(tmpDir, "README.md"))
assert.NoError(t, err)
assert.False(t, exist)
}
}
func doGitInitTestRepository(dstPath string) func(*testing.T) {
return func(t *testing.T) {
// Init repository in dstPath
assert.NoError(t, git.InitRepository(t.Context(), dstPath, false, git.Sha1ObjectFormat.Name()))
// forcibly set default branch to master
_, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD", git.BranchPrefix+"master").
WithDir(dstPath).
RunStdString(t.Context())
assert.NoError(t, err)
assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte("# Testing Repository\n\nOriginally created in: "+dstPath), 0o644))
assert.NoError(t, gitAddChangesDeprecated(t.Context(), dstPath, true))
signature := git.Signature{
Email: "[email protected]",
Name: "test",
When: time.Now(),
}
assert.NoError(t, gitCommitChangesDeprecated(t.Context(), dstPath, gitCommitChangesOptions{
Committer: &signature,
Author: &signature,
Message: "Initial Commit",
}))
}
}
func doGitAddRemote(dstPath, remoteName string, u *url.URL) func(*testing.T) {
return func(t *testing.T) {
_, _, err := gitcmd.NewCommand("remote", "add").AddDynamicArguments(remoteName, u.String()).
WithDir(dstPath).
RunStdString(t.Context())
assert.NoError(t, err)
}
}
func doGitPushTestRepository(dstPath string, args ...string) func(*testing.T) {
return func(t *testing.T) {
_, _, err := gitcmd.NewCommand("push", "-u").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
WithDir(dstPath).
RunStdString(t.Context())
assert.NoError(t, err)
}
}
func doGitPushTestRepositoryFail(dstPath string, args ...string) func(*testing.T) {
return func(t *testing.T) {
_, _, err := gitcmd.NewCommand("push").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
WithDir(dstPath).
RunStdString(t.Context())
assert.Error(t, err)
}
}
type localGitAddCommitOptions struct {
LocalRepoPath string
CheckoutBranch string
TreeFilePath string
TreeFileContent string
}
func doGitCheckoutWriteFileCommit(opts localGitAddCommitOptions) func(*testing.T) {
return func(t *testing.T) {
doGitCheckoutBranch(opts.LocalRepoPath, opts.CheckoutBranch)(t)
localFilePath := filepath.Join(opts.LocalRepoPath, opts.TreeFilePath)
require.NoError(t, os.WriteFile(localFilePath, []byte(opts.TreeFileContent), 0o644))
require.NoError(t, gitAddChangesDeprecated(t.Context(), opts.LocalRepoPath, true))
signature := git.Signature{
Email: "[email protected]",
Name: "test",
}
require.NoError(t, gitCommitChangesDeprecated(t.Context(), opts.LocalRepoPath, gitCommitChangesOptions{
Committer: &signature,
Author: &signature,
Message: fmt.Sprintf("update %s @ %s", opts.TreeFilePath, opts.CheckoutBranch),
}))
}
}
func doGitCreateBranch(dstPath, branch string) func(*testing.T) {
return func(t *testing.T) {
_, _, err := gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(branch).
WithDir(dstPath).RunStdString(t.Context())
assert.NoError(t, err)
}
}
func doGitCheckoutBranch(dstPath string, args ...string) func(*testing.T) {
return func(t *testing.T) {
_, _, err := gitcmd.NewCommand().AddArguments("checkout").
AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
WithDir(dstPath).
RunStdString(t.Context())
assert.NoError(t, err)
}
}
func doGitMerge(dstPath string, args ...string) func(*testing.T) {
return func(t *testing.T) {
_, _, err := gitcmd.NewCommand("merge").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
WithDir(dstPath).
RunStdString(t.Context())
assert.NoError(t, err)
}
}
func doGitPull(dstPath string, args ...string) func(*testing.T) {
return func(t *testing.T) {
_, _, err := gitcmd.NewCommand().AddArguments("pull").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
WithDir(dstPath).
RunStdString(t.Context())
assert.NoError(t, err)
}
}
// doGitRemoteArchive runs a git archive command requesting an archive from remote
// and verifies that the command did not error out and returned only normal output
func doGitRemoteArchive(remote string, args ...string) func(*testing.T) {
return func(t *testing.T) {
stdout, stderr, err := gitcmd.NewCommand("archive").AddOptionValues("--remote", remote).AddArguments(gitcmd.ToTrustedCmdArgs(args)...).
RunStdString(t.Context())
require.NoError(t, err)
assert.Empty(t, stderr)
assert.NotEmpty(t, stdout)
}
}