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}