Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/content-service/pkg/git/git_test.go
2500 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package git
6
7
import (
8
"context"
9
"os"
10
"path/filepath"
11
"strings"
12
"testing"
13
"time"
14
15
"github.com/google/go-cmp/cmp"
16
"golang.org/x/xerrors"
17
)
18
19
const (
20
notEmpty = "not-empty"
21
)
22
23
func TestGitStatus(t *testing.T) {
24
tests := []struct {
25
Name string
26
Prep func(context.Context, *Client) error
27
Result *Status
28
Error error
29
}{
30
{
31
"no commits",
32
func(ctx context.Context, c *Client) error {
33
if err := c.Git(ctx, "init"); err != nil {
34
return err
35
}
36
return nil
37
},
38
&Status{
39
porcelainStatus: porcelainStatus{
40
BranchOID: "(initial)",
41
BranchHead: "master",
42
},
43
},
44
nil,
45
},
46
{
47
"clean copy",
48
func(ctx context.Context, c *Client) error {
49
if err := initFromRemote(ctx, c); err != nil {
50
return err
51
}
52
return nil
53
},
54
&Status{
55
porcelainStatus: porcelainStatus{
56
BranchHead: "master",
57
BranchOID: notEmpty,
58
},
59
LatestCommit: notEmpty,
60
},
61
nil,
62
},
63
{
64
"untracked files",
65
func(ctx context.Context, c *Client) error {
66
if err := initFromRemote(ctx, c); err != nil {
67
return err
68
}
69
if err := os.WriteFile(filepath.Join(c.Location, "another-file"), []byte{}, 0755); err != nil {
70
return err
71
}
72
return nil
73
},
74
&Status{
75
porcelainStatus: porcelainStatus{
76
BranchHead: "master",
77
BranchOID: notEmpty,
78
UntrackedFiles: []string{"another-file"},
79
},
80
LatestCommit: notEmpty,
81
},
82
nil,
83
},
84
{
85
"uncommitted files",
86
func(ctx context.Context, c *Client) error {
87
if err := initFromRemote(ctx, c); err != nil {
88
return err
89
}
90
if err := os.WriteFile(filepath.Join(c.Location, "first-file"), []byte("foobar"), 0755); err != nil {
91
return err
92
}
93
return nil
94
},
95
&Status{
96
porcelainStatus: porcelainStatus{
97
BranchHead: "master",
98
BranchOID: notEmpty,
99
UncommitedFiles: []string{"first-file"},
100
},
101
LatestCommit: notEmpty,
102
},
103
nil,
104
},
105
{
106
"unpushed commits",
107
func(ctx context.Context, c *Client) error {
108
if err := initFromRemote(ctx, c); err != nil {
109
return err
110
}
111
if err := os.WriteFile(filepath.Join(c.Location, "first-file"), []byte("foobar"), 0755); err != nil {
112
return err
113
}
114
if err := c.Git(ctx, "commit", "-a", "-m", "foo"); err != nil {
115
return err
116
}
117
return nil
118
},
119
&Status{
120
porcelainStatus: porcelainStatus{
121
BranchHead: "master",
122
BranchOID: notEmpty,
123
},
124
UnpushedCommits: []string{notEmpty},
125
LatestCommit: notEmpty,
126
},
127
nil,
128
},
129
{
130
"unpushed commits in new branch",
131
func(ctx context.Context, c *Client) error {
132
if err := initFromRemote(ctx, c); err != nil {
133
return err
134
}
135
if err := c.Git(ctx, "checkout", "-b", "otherbranch"); err != nil {
136
return err
137
}
138
if err := os.WriteFile(filepath.Join(c.Location, "first-file"), []byte("foobar"), 0755); err != nil {
139
return err
140
}
141
if err := c.Git(ctx, "commit", "-a", "-m", "foo"); err != nil {
142
return err
143
}
144
return nil
145
},
146
&Status{
147
porcelainStatus: porcelainStatus{
148
BranchHead: "otherbranch",
149
BranchOID: notEmpty,
150
},
151
UnpushedCommits: []string{notEmpty},
152
LatestCommit: notEmpty,
153
},
154
nil,
155
},
156
157
{
158
"pending in sub-dir files",
159
func(ctx context.Context, c *Client) error {
160
if err := initFromRemote(ctx, c); err != nil {
161
return err
162
}
163
if err := os.MkdirAll(filepath.Join(c.Location, "this/is/a/nested/test"), 0755); err != nil {
164
return err
165
}
166
if err := os.WriteFile(filepath.Join(c.Location, "this/is/a/nested/test/first-file"), []byte("foobar"), 0755); err != nil {
167
return err
168
}
169
return nil
170
},
171
&Status{
172
porcelainStatus: porcelainStatus{
173
BranchHead: "master",
174
BranchOID: notEmpty,
175
UntrackedFiles: []string{"this/is/a/nested/test/first-file"},
176
},
177
LatestCommit: notEmpty,
178
},
179
nil,
180
},
181
}
182
183
for _, test := range tests {
184
t.Run(test.Name, func(t *testing.T) {
185
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
186
defer cancel()
187
188
client, err := newGitClient(ctx)
189
if err != nil {
190
t.Errorf("cannot prep %s: %v", test.Name, err)
191
return
192
}
193
194
err = test.Prep(ctx, client)
195
if err != nil {
196
t.Errorf("cannot prep %s: %v", test.Name, err)
197
return
198
}
199
200
status, err := client.Status(ctx)
201
if err != test.Error {
202
t.Errorf("expected error does not match for %s: %v != %v", test.Name, err, test.Error)
203
return
204
}
205
206
if status != nil {
207
if test.Result.BranchOID == notEmpty && status.LatestCommit != "" {
208
test.Result.BranchOID = status.LatestCommit
209
}
210
if test.Result.LatestCommit == notEmpty && status.LatestCommit != "" {
211
test.Result.LatestCommit = status.LatestCommit
212
}
213
for _, c := range test.Result.UnpushedCommits {
214
if c == notEmpty {
215
if len(status.UnpushedCommits) == 0 {
216
t.Errorf("expected unpushed commits")
217
}
218
219
test.Result.UnpushedCommits = status.UnpushedCommits
220
break
221
}
222
}
223
}
224
225
if diff := cmp.Diff(test.Result, status, cmp.AllowUnexported(Status{})); diff != "" {
226
t.Errorf("unexpected status (-want +got):\n%s", diff)
227
}
228
})
229
}
230
}
231
232
func TestGitStatusFromFiles(t *testing.T) {
233
tests := []struct {
234
Name string
235
Prep func(context.Context, *Client) error
236
Result *Status
237
Error error
238
}{
239
{
240
"no commits",
241
func(ctx context.Context, c *Client) error {
242
if err := c.Git(ctx, "init"); err != nil {
243
return err
244
}
245
return nil
246
},
247
&Status{
248
porcelainStatus: porcelainStatus{
249
BranchOID: "(initial)",
250
BranchHead: "master",
251
},
252
},
253
nil,
254
},
255
{
256
"clean copy",
257
func(ctx context.Context, c *Client) error {
258
if err := initFromRemote(ctx, c); err != nil {
259
return err
260
}
261
return nil
262
},
263
&Status{
264
porcelainStatus: porcelainStatus{
265
BranchHead: "master",
266
BranchOID: notEmpty,
267
},
268
LatestCommit: notEmpty,
269
},
270
nil,
271
},
272
{
273
"untracked files",
274
func(ctx context.Context, c *Client) error {
275
if err := initFromRemote(ctx, c); err != nil {
276
return err
277
}
278
if err := os.WriteFile(filepath.Join(c.Location, "another-file"), []byte{}, 0755); err != nil {
279
return err
280
}
281
return nil
282
},
283
&Status{
284
porcelainStatus: porcelainStatus{
285
BranchHead: "master",
286
BranchOID: notEmpty,
287
UntrackedFiles: []string{"another-file"},
288
},
289
LatestCommit: notEmpty,
290
},
291
nil,
292
},
293
{
294
"uncommitted files",
295
func(ctx context.Context, c *Client) error {
296
if err := initFromRemote(ctx, c); err != nil {
297
return err
298
}
299
if err := os.WriteFile(filepath.Join(c.Location, "first-file"), []byte("foobar"), 0755); err != nil {
300
return err
301
}
302
return nil
303
},
304
&Status{
305
porcelainStatus: porcelainStatus{
306
BranchHead: "master",
307
BranchOID: notEmpty,
308
UncommitedFiles: []string{"first-file"},
309
},
310
LatestCommit: notEmpty,
311
},
312
nil,
313
},
314
{
315
"unpushed commits",
316
func(ctx context.Context, c *Client) error {
317
if err := initFromRemote(ctx, c); err != nil {
318
return err
319
}
320
if err := os.WriteFile(filepath.Join(c.Location, "first-file"), []byte("foobar"), 0755); err != nil {
321
return err
322
}
323
if err := c.Git(ctx, "commit", "-a", "-m", "foo"); err != nil {
324
return err
325
}
326
return nil
327
},
328
&Status{
329
porcelainStatus: porcelainStatus{
330
BranchHead: "master",
331
BranchOID: notEmpty,
332
},
333
UnpushedCommits: []string{notEmpty},
334
LatestCommit: notEmpty,
335
},
336
nil,
337
},
338
{
339
"unpushed commits in new branch",
340
func(ctx context.Context, c *Client) error {
341
if err := initFromRemote(ctx, c); err != nil {
342
return err
343
}
344
if err := c.Git(ctx, "checkout", "-b", "otherbranch"); err != nil {
345
return err
346
}
347
if err := os.WriteFile(filepath.Join(c.Location, "first-file"), []byte("foobar"), 0755); err != nil {
348
return err
349
}
350
if err := c.Git(ctx, "commit", "-a", "-m", "foo"); err != nil {
351
return err
352
}
353
return nil
354
},
355
&Status{
356
porcelainStatus: porcelainStatus{
357
BranchHead: "otherbranch",
358
BranchOID: notEmpty,
359
},
360
UnpushedCommits: []string{notEmpty},
361
LatestCommit: notEmpty,
362
},
363
nil,
364
},
365
366
{
367
"pending in sub-dir files",
368
func(ctx context.Context, c *Client) error {
369
if err := initFromRemote(ctx, c); err != nil {
370
return err
371
}
372
if err := os.MkdirAll(filepath.Join(c.Location, "this/is/a/nested/test"), 0755); err != nil {
373
return err
374
}
375
if err := os.WriteFile(filepath.Join(c.Location, "this/is/a/nested/test/first-file"), []byte("foobar"), 0755); err != nil {
376
return err
377
}
378
return nil
379
},
380
&Status{
381
porcelainStatus: porcelainStatus{
382
BranchHead: "master",
383
BranchOID: notEmpty,
384
UntrackedFiles: []string{"this/is/a/nested/test/first-file"},
385
},
386
LatestCommit: notEmpty,
387
},
388
nil,
389
},
390
}
391
392
for _, test := range tests {
393
t.Run(test.Name, func(t *testing.T) {
394
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
395
defer cancel()
396
397
statusLocation, err := os.MkdirTemp("", "git-status")
398
if err != nil {
399
t.Errorf("cannot create temporal directory: %v", err)
400
return
401
}
402
403
defer func() {
404
os.RemoveAll(statusLocation)
405
}()
406
407
client, err := newGitClient(ctx)
408
if err != nil {
409
t.Errorf("cannot prep %s: %v", test.Name, err)
410
return
411
}
412
413
err = test.Prep(ctx, client)
414
if err != nil {
415
t.Errorf("cannot prep %s: %v", test.Name, err)
416
return
417
}
418
419
gitout, err := client.GitWithOutput(ctx, nil, "status", "--porcelain=v2", "--branch", "-uall")
420
if err != nil {
421
t.Errorf("error calling GitWithOutput: %v", err)
422
return
423
}
424
if err := os.WriteFile(filepath.Join(statusLocation, "git_status.txt"), gitout, 0755); err != nil {
425
t.Errorf("error creating file: %v", err)
426
return
427
}
428
429
gitout, err = client.GitWithOutput(ctx, &errNoCommitsYet, "log", "--pretty=%h: %s", "--branches", "--not", "--remotes")
430
if err != nil {
431
t.Errorf("error calling GitWithOutput: %v", err)
432
return
433
}
434
if err := os.WriteFile(filepath.Join(statusLocation, "git_log_1.txt"), gitout, 0755); err != nil {
435
t.Errorf("error creating file: %v", err)
436
return
437
}
438
439
gitout, err = client.GitWithOutput(ctx, &errNoCommitsYet, "log", "--pretty=%H", "-n", "1")
440
if err != nil && !strings.Contains(err.Error(), "fatal: your current branch 'master' does not have any commits yet") {
441
t.Errorf("error calling GitWithOutput: %v", err)
442
return
443
}
444
if err := os.WriteFile(filepath.Join(statusLocation, "git_log_2.txt"), gitout, 0755); err != nil {
445
t.Errorf("error creating file: %v", err)
446
return
447
}
448
449
status, err := GitStatusFromFiles(ctx, statusLocation)
450
if err != test.Error {
451
t.Errorf("expected error does not match for %s: %v != %v", test.Name, err, test.Error)
452
return
453
}
454
455
if status != nil {
456
if test.Result.BranchOID == notEmpty && status.LatestCommit != "" {
457
test.Result.BranchOID = status.LatestCommit
458
}
459
if test.Result.LatestCommit == notEmpty && status.LatestCommit != "" {
460
test.Result.LatestCommit = status.LatestCommit
461
}
462
for _, c := range test.Result.UnpushedCommits {
463
if c == notEmpty {
464
if len(status.UnpushedCommits) == 0 {
465
t.Errorf("expected unpushed commits")
466
}
467
468
test.Result.UnpushedCommits = status.UnpushedCommits
469
break
470
}
471
}
472
}
473
474
if diff := cmp.Diff(test.Result, status, cmp.AllowUnexported(Status{})); diff != "" {
475
t.Errorf("unexpected status (-want +got):\n%s", diff)
476
}
477
478
})
479
}
480
}
481
482
func newGitClient(ctx context.Context) (*Client, error) {
483
loc, err := os.MkdirTemp("", "gittest")
484
if err != nil {
485
return nil, err
486
}
487
488
return &Client{
489
Location: loc,
490
Config: map[string]string{
491
"user.email": "[email protected]",
492
"user.name": "tester",
493
},
494
}, nil
495
}
496
497
func initFromRemote(ctx context.Context, c *Client) error {
498
remote, err := newGitClient(ctx)
499
if err != nil {
500
return xerrors.Errorf("cannot add remote: %w", err)
501
}
502
if err := remote.Git(ctx, "init"); err != nil {
503
return err
504
}
505
if err := remote.Git(ctx, "config", "--local", "user.email", "[email protected]"); err != nil {
506
return err
507
}
508
if err := remote.Git(ctx, "config", "--local", "user.name", "foo bar"); err != nil {
509
return err
510
}
511
if err := os.WriteFile(filepath.Join(remote.Location, "first-file"), []byte{}, 0755); err != nil {
512
return err
513
}
514
if err := remote.Git(ctx, "add", "first-file"); err != nil {
515
return err
516
}
517
if err := remote.Git(ctx, "commit", "-m", "foo"); err != nil {
518
return err
519
}
520
521
c.RemoteURI = remote.Location
522
if err := c.Clone(ctx); err != nil {
523
return err
524
}
525
if err := c.Git(ctx, "config", "--local", "user.email", "[email protected]"); err != nil {
526
return err
527
}
528
if err := c.Git(ctx, "config", "--local", "user.name", "foo bar"); err != nil {
529
return err
530
}
531
532
return nil
533
}
534
535