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// CreatePubkey returns an armored public PGP key.
 52func CreatePubkey(t *testing.T) string {
 53	// Generate a key pair for signing commits.
 54	pgpEntity, err := openpgp.NewEntity("First Last", "", "fl@example.org", nil)
 55	require.NoError(t, err)
 56
 57	// Armor the public part.
 58	pubBuilder := &strings.Builder{}
 59	w, err := armor.Encode(pubBuilder, openpgp.PublicKeyType, nil)
 60	require.NoError(t, err)
 61	err = pgpEntity.Serialize(w)
 62	require.NoError(t, err)
 63	err = w.Close()
 64	require.NoError(t, err)
 65	armoredPub := pubBuilder.String()
 66	return armoredPub
 67}
 68
 69// SetupSigningKey creates a GPG key and sets up the local config so it's used.
 70// The key id is set as "user.signingkey". For the key to be found, a `gpg`
 71// wrapper which uses only a custom keyring is created and set as "gpg.program".
 72// Finally "commit.gpgsign" is set to true so the signing takes place.
 73//
 74// Returns the armored public key.
 75func SetupSigningKey(t *testing.T, repo *GitRepo, email string) string {
 76	keyId, armoredPub, gpgWrapper := CreateKey(t, email)
 77
 78	SetupKey(t, repo, email, keyId, gpgWrapper)
 79
 80	return armoredPub
 81}
 82
 83func SetupKey(t *testing.T, repo *GitRepo, email, keyId, gpgWrapper string) {
 84	config := repo.LocalConfig()
 85
 86	if email != "" {
 87		err := config.StoreString("user.email", email)
 88		require.NoError(t, err)
 89	}
 90
 91	if keyId != "" {
 92		err := config.StoreString("user.signingkey", keyId)
 93		require.NoError(t, err)
 94	}
 95
 96	if gpgWrapper != "" {
 97		err := config.StoreString("gpg.program", gpgWrapper)
 98		require.NoError(t, err)
 99	}
100
101	err := config.StoreString("commit.gpgsign", "true")
102	require.NoError(t, err)
103}
104
105func CreateKey(t *testing.T, email string) (keyId, armoredPub, gpgWrapper string) {
106	// Generate a key pair for signing commits.
107	entity, err := openpgp.NewEntity("First Last", "", email, nil)
108	require.NoError(t, err)
109
110	keyId = entity.PrivateKey.KeyIdString()
111
112	// Armor the private part.
113	privBuilder := &strings.Builder{}
114	w, err := armor.Encode(privBuilder, openpgp.PrivateKeyType, nil)
115	require.NoError(t, err)
116	err = entity.SerializePrivate(w, nil)
117	require.NoError(t, err)
118	err = w.Close()
119	require.NoError(t, err)
120	armoredPriv := privBuilder.String()
121
122	// Armor the public part.
123	pubBuilder := &strings.Builder{}
124	w, err = armor.Encode(pubBuilder, openpgp.PublicKeyType, nil)
125	require.NoError(t, err)
126	err = entity.Serialize(w)
127	require.NoError(t, err)
128	err = w.Close()
129	require.NoError(t, err)
130	armoredPub = pubBuilder.String()
131
132	// Create a custom gpg keyring to be used when creating commits with `git`.
133	keyring, err := ioutil.TempFile("", "keyring")
134	require.NoError(t, err)
135
136	// Import the armored private key to the custom keyring.
137	priv, err := ioutil.TempFile("", "privkey")
138	require.NoError(t, err)
139	_, err = fmt.Fprint(priv, armoredPriv)
140	require.NoError(t, err)
141	err = priv.Close()
142	require.NoError(t, err)
143	err = exec.Command("gpg", "--no-default-keyring", "--keyring", keyring.Name(), "--import", priv.Name()).Run()
144	require.NoError(t, err)
145
146	// Import the armored public key to the custom keyring.
147	pub, err := ioutil.TempFile("", "pubkey")
148	require.NoError(t, err)
149	_, err = fmt.Fprint(pub, armoredPub)
150	require.NoError(t, err)
151	err = pub.Close()
152	require.NoError(t, err)
153	err = exec.Command("gpg", "--no-default-keyring", "--keyring", keyring.Name(), "--import", pub.Name()).Run()
154	require.NoError(t, err)
155
156	// Use a gpg wrapper to use a custom keyring containing GPGKeyID.
157	gpgWrapper = createGPGWrapper(t, keyring.Name())
158
159	return
160}
161
162// createGPGWrapper creates a shell script running gpg with a specific keyring.
163func createGPGWrapper(t *testing.T, keyringPath string) string {
164	file, err := ioutil.TempFile("", "gpgwrapper")
165	require.NoError(t, err)
166
167	_, err = fmt.Fprintf(file, `#!/bin/sh
168exec gpg --no-default-keyring --keyring="%s" "$@"
169`, keyringPath)
170	require.NoError(t, err)
171
172	err = file.Close()
173	require.NoError(t, err)
174
175	err = os.Chmod(file.Name(), os.FileMode(0700))
176	require.NoError(t, err)
177
178	return file.Name()
179}
180
181func CleanupTestRepos(t testing.TB, repos ...Repo) {
182	var firstErr error
183	for _, repo := range repos {
184		path := repo.GetPath()
185		if strings.HasSuffix(path, "/.git") {
186			// for a normal repository (not --bare), we want to remove everything
187			// including the parent directory where files are checked out
188			path = strings.TrimSuffix(path, "/.git")
189
190			// Testing non-bare repo should also check path is
191			// only .git (i.e. ./.git), but doing so, we should
192			// try to remove the current directory and hav some
193			// trouble. In the present case, this case should not
194			// occur.
195			// TODO consider warning or error when path == ".git"
196		}
197		// fmt.Println("Cleaning repo:", path)
198		err := os.RemoveAll(path)
199		if err != nil {
200			log.Println(err)
201			if firstErr == nil {
202				firstErr = err
203			}
204		}
205	}
206
207	if firstErr != nil {
208		t.Fatal(firstErr)
209	}
210}
211
212func SetupReposAndRemote(t testing.TB) (repoA, repoB, remote *GitRepo) {
213	repoA = CreateTestRepo(false)
214	repoB = CreateTestRepo(false)
215	remote = CreateTestRepo(true)
216
217	remoteAddr := "file://" + remote.GetPath()
218
219	err := repoA.AddRemote("origin", remoteAddr)
220	if err != nil {
221		t.Fatal(err)
222	}
223
224	err = repoB.AddRemote("origin", remoteAddr)
225	if err != nil {
226		t.Fatal(err)
227	}
228
229	return repoA, repoB, remote
230}