git_testing.go

  1package repository
  2
  3import (
  4	"fmt"
  5	"io/ioutil"
  6	"log"
  7	"os"
  8	"os/exec"
  9	"strings"
 10	"testing"
 11
 12	"github.com/stretchr/testify/require"
 13	"golang.org/x/crypto/openpgp"
 14	"golang.org/x/crypto/openpgp/armor"
 15)
 16
 17// This is intended for testing only
 18
 19func CreateTestRepo(bare bool) *GitRepo {
 20	dir, err := ioutil.TempDir("", "")
 21	if err != nil {
 22		log.Fatal(err)
 23	}
 24
 25	// fmt.Println("Creating repo:", dir)
 26
 27	var creator func(string) (*GitRepo, error)
 28
 29	if bare {
 30		creator = InitBareGitRepo
 31	} else {
 32		creator = InitGitRepo
 33	}
 34
 35	repo, err := creator(dir)
 36	if err != nil {
 37		log.Fatal(err)
 38	}
 39
 40	config := repo.LocalConfig()
 41	if err := config.StoreString("user.name", "testuser"); err != nil {
 42		log.Fatal("failed to set user.name for test repository: ", err)
 43	}
 44	if err := config.StoreString("user.email", "testuser@example.com"); err != nil {
 45		log.Fatal("failed to set user.email for test repository: ", err)
 46	}
 47
 48	return repo
 49}
 50
 51// setupSigningKey creates a GPG key and sets up the local config so it's used.
 52// The key id is set as "user.signingkey". For the key to be found, a `gpg`
 53// wrapper which uses only a custom keyring is created and set as "gpg.program".
 54// Finally "commit.gpgsign" is set to true so the signing takes place.
 55func setupSigningKey(t *testing.T, repo *GitRepo) {
 56	config := repo.LocalConfig()
 57
 58	// Generate a key pair for signing commits.
 59	entity, err := openpgp.NewEntity("First Last", "", "fl@example.org", nil)
 60	require.NoError(t, err)
 61
 62	err = config.StoreString("user.signingkey", entity.PrivateKey.KeyIdString())
 63	require.NoError(t, err)
 64
 65	// Armor the private part.
 66	privBuilder := &strings.Builder{}
 67	w, err := armor.Encode(privBuilder, openpgp.PrivateKeyType, nil)
 68	require.NoError(t, err)
 69	err = entity.SerializePrivate(w, nil)
 70	require.NoError(t, err)
 71	err = w.Close()
 72	require.NoError(t, err)
 73	armoredPriv := privBuilder.String()
 74
 75	// Armor the public part.
 76	pubBuilder := &strings.Builder{}
 77	w, err = armor.Encode(pubBuilder, openpgp.PublicKeyType, nil)
 78	require.NoError(t, err)
 79	err = entity.Serialize(w)
 80	require.NoError(t, err)
 81	err = w.Close()
 82	require.NoError(t, err)
 83	armoredPub := pubBuilder.String()
 84
 85	// Create a custom gpg keyring to be used when creating commits with `git`.
 86	keyring, err := ioutil.TempFile("", "keyring")
 87	require.NoError(t, err)
 88
 89	// Import the armored private key to the custom keyring.
 90	priv, err := ioutil.TempFile("", "privkey")
 91	require.NoError(t, err)
 92	_, err = fmt.Fprint(priv, armoredPriv)
 93	require.NoError(t, err)
 94	err = priv.Close()
 95	require.NoError(t, err)
 96	err = exec.Command("gpg", "--no-default-keyring", "--keyring", keyring.Name(), "--import", priv.Name()).Run()
 97	require.NoError(t, err)
 98
 99	// Import the armored public key to the custom keyring.
100	pub, err := ioutil.TempFile("", "pubkey")
101	require.NoError(t, err)
102	_, err = fmt.Fprint(pub, armoredPub)
103	require.NoError(t, err)
104	err = pub.Close()
105	require.NoError(t, err)
106	err = exec.Command("gpg", "--no-default-keyring", "--keyring", keyring.Name(), "--import", pub.Name()).Run()
107	require.NoError(t, err)
108
109	// Use a gpg wrapper to use a custom keyring containing GPGKeyID.
110	gpgWrapper := createGPGWrapper(t, keyring.Name())
111	if err := config.StoreString("gpg.program", gpgWrapper); err != nil {
112		log.Fatal("failed to set gpg.program for test repository: ", err)
113	}
114
115	if err := config.StoreString("commit.gpgsign", "true"); err != nil {
116		log.Fatal("failed to set commit.gpgsign for test repository: ", err)
117	}
118}
119
120// createGPGWrapper creates a shell script running gpg with a specific keyring.
121func createGPGWrapper(t *testing.T, keyringPath string) string {
122	file, err := ioutil.TempFile("", "gpgwrapper")
123	require.NoError(t, err)
124
125	_, err = fmt.Fprintf(file, `#!/bin/sh
126exec gpg --no-default-keyring --keyring="%s" "$@"
127`, keyringPath)
128	require.NoError(t, err)
129
130	err = file.Close()
131	require.NoError(t, err)
132
133	err = os.Chmod(file.Name(), os.FileMode(0700))
134	require.NoError(t, err)
135
136	return file.Name()
137}
138
139func CleanupTestRepos(t testing.TB, repos ...Repo) {
140	var firstErr error
141	for _, repo := range repos {
142		path := repo.GetPath()
143		if strings.HasSuffix(path, "/.git") {
144			// for a normal repository (not --bare), we want to remove everything
145			// including the parent directory where files are checked out
146			path = strings.TrimSuffix(path, "/.git")
147
148			// Testing non-bare repo should also check path is
149			// only .git (i.e. ./.git), but doing so, we should
150			// try to remove the current directory and hav some
151			// trouble. In the present case, this case should not
152			// occur.
153			// TODO consider warning or error when path == ".git"
154		}
155		// fmt.Println("Cleaning repo:", path)
156		err := os.RemoveAll(path)
157		if err != nil {
158			log.Println(err)
159			if firstErr == nil {
160				firstErr = err
161			}
162		}
163	}
164
165	if firstErr != nil {
166		t.Fatal(firstErr)
167	}
168}
169
170func SetupReposAndRemote(t testing.TB) (repoA, repoB, remote *GitRepo) {
171	repoA = CreateTestRepo(false)
172	repoB = CreateTestRepo(false)
173	remote = CreateTestRepo(true)
174
175	remoteAddr := "file://" + remote.GetPath()
176
177	err := repoA.AddRemote("origin", remoteAddr)
178	if err != nil {
179		t.Fatal(err)
180	}
181
182	err = repoB.AddRemote("origin", remoteAddr)
183	if err != nil {
184		t.Fatal(err)
185	}
186
187	return repoA, repoB, remote
188}