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}