Skip to content

Commit 1bc26f3

Browse files
heschigopherbot
authored andcommitted
internal/task: create a basic Git wrapper, use it for the fake Gerrit
For golang/go#59717 Change-Id: Ie0e01a82a5acebf279c1f770dee53f37fc4e7800 Reviewed-on: https://go-review.googlesource.com/c/build/+/486515 Run-TryBot: Heschi Kreinick <[email protected]> Auto-Submit: Heschi Kreinick <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 84c18c5 commit 1bc26f3

File tree

5 files changed

+182
-82
lines changed

5 files changed

+182
-82
lines changed

internal/relui/buildrelease_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ type releaseTestDeps struct {
103103
publishedFiles map[string]task.WebsiteFile
104104
}
105105

106-
func newReleaseTestDeps(t *testing.T, previousTag, wantVersion string) *releaseTestDeps {
106+
func newReleaseTestDeps(t *testing.T, previousTag string, major int, wantVersion string) *releaseTestDeps {
107107
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
108108
t.Skip("Requires bash shell scripting support.")
109109
}
@@ -177,6 +177,7 @@ esac
177177
goRepo := task.NewFakeRepo(t, "go")
178178
base := goRepo.Commit(goFiles)
179179
goRepo.Tag(previousTag, base)
180+
goRepo.Branch(fmt.Sprintf("release-branch.go1.%d", major), base)
180181
dlRepo := task.NewFakeRepo(t, "dl")
181182
toolsRepo := task.NewFakeRepo(t, "tools")
182183
toolsRepo1 := toolsRepo.Commit(map[string]string{
@@ -256,7 +257,7 @@ esac
256257
}
257258

258259
func testRelease(t *testing.T, prevTag string, major int, wantVersion string, kind task.ReleaseKind) {
259-
deps := newReleaseTestDeps(t, prevTag, wantVersion)
260+
deps := newReleaseTestDeps(t, prevTag, major, wantVersion)
260261
wd := workflow.New()
261262

262263
deps.gerrit.wantReviewers = []string{"heschi", "dmitshur"}
@@ -417,7 +418,7 @@ func testRelease(t *testing.T, prevTag string, major int, wantVersion string, ki
417418
}
418419

419420
func testSecurity(t *testing.T, mergeFixes bool) {
420-
deps := newReleaseTestDeps(t, "go1.17", "go1.18rc1")
421+
deps := newReleaseTestDeps(t, "go1.17", 18, "go1.18rc1")
421422

422423
// Set up the fake merge process. Once we stop to ask for approval, commit
423424
// the fix to the public server.
@@ -431,7 +432,7 @@ func testSecurity(t *testing.T, mergeFixes bool) {
431432
defaultApprove := deps.buildTasks.ApproveAction
432433
deps.buildTasks.ApproveAction = func(tc *workflow.TaskContext) error {
433434
if mergeFixes {
434-
deps.goRepo.Commit(securityFix)
435+
deps.goRepo.CommitOnBranch("release-branch.go1.18", securityFix)
435436
}
436437
return defaultApprove(tc)
437438
}
@@ -468,7 +469,7 @@ func testSecurity(t *testing.T, mergeFixes bool) {
468469
}
469470

470471
func TestAdvisoryTrybotFail(t *testing.T) {
471-
deps := newReleaseTestDeps(t, "go1.17", "go1.18rc1")
472+
deps := newReleaseTestDeps(t, "go1.17", 18, "go1.18rc1")
472473
defaultApprove := deps.buildTasks.ApproveAction
473474
var trybotApprovals atomic.Int32
474475
deps.buildTasks.ApproveAction = func(ctx *workflow.TaskContext) error {

internal/task/fakes.go

Lines changed: 76 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"compress/gzip"
1111
"context"
1212
"crypto/sha256"
13+
"errors"
1314
"fmt"
1415
"io"
1516
"io/ioutil"
@@ -348,67 +349,70 @@ type FakeGerrit struct {
348349
}
349350

350351
type FakeRepo struct {
351-
t *testing.T
352-
name string
353-
seq int
354-
history []string // oldest to newest
355-
content map[string]map[string]string
356-
tags map[string]string
352+
t *testing.T
353+
name string
354+
dir *GitDir
357355
}
358356

359357
func NewFakeRepo(t *testing.T, name string) *FakeRepo {
360-
return &FakeRepo{
361-
t: t,
362-
name: name,
363-
content: map[string]map[string]string{},
364-
tags: map[string]string{},
358+
if _, err := exec.LookPath("git"); errors.Is(err, exec.ErrNotFound) {
359+
t.Skip("test requires git")
365360
}
361+
362+
r := &FakeRepo{
363+
t: t,
364+
name: name,
365+
dir: &GitDir{&Git{}, t.TempDir()},
366+
}
367+
t.Cleanup(func() { r.dir.Close() })
368+
r.runGit("init", "-b", "master")
369+
r.runGit("commit", "--allow-empty", "--allow-empty-message", "-m", "")
370+
return r
371+
}
372+
373+
func (repo *FakeRepo) runGit(args ...string) []byte {
374+
repo.t.Helper()
375+
out, err := repo.dir.RunCommand(context.Background(), args...)
376+
if err != nil {
377+
repo.t.Fatal(err)
378+
}
379+
return out
366380
}
367381

368382
func (repo *FakeRepo) Commit(contents map[string]string) string {
369-
rev := fmt.Sprintf("%v~%v", repo.name, repo.seq)
370-
repo.seq++
383+
return repo.CommitOnBranch("master", contents)
384+
}
371385

372-
newContent := map[string]string{}
373-
if len(repo.history) != 0 {
374-
for k, v := range repo.content[repo.history[len(repo.history)-1]] {
375-
newContent[k] = v
376-
}
377-
}
386+
func (repo *FakeRepo) CommitOnBranch(branch string, contents map[string]string) string {
387+
repo.runGit("switch", branch)
378388
for k, v := range contents {
379-
newContent[k] = v
389+
full := filepath.Join(repo.dir.dir, k)
390+
if err := os.MkdirAll(filepath.Dir(full), 0777); err != nil {
391+
repo.t.Fatal(err)
392+
}
393+
if err := os.WriteFile(full, []byte(v), 0777); err != nil {
394+
repo.t.Fatal(err)
395+
}
380396
}
381-
repo.content[rev] = newContent
382-
repo.history = append(repo.history, rev)
383-
return rev
397+
repo.runGit("add", ".")
398+
repo.runGit("commit", "--allow-empty-message", "-m", "")
399+
return strings.TrimSpace(string(repo.runGit("rev-parse", "HEAD")))
400+
}
401+
402+
func (repo *FakeRepo) History() []string {
403+
return strings.Split(string(repo.runGit("log", "--format=%H")), "\n")
384404
}
385405

386406
func (repo *FakeRepo) Tag(tag, commit string) {
387-
if _, ok := repo.content[commit]; !ok {
388-
repo.t.Fatalf("commit %q does not exist on repo %q", commit, repo.name)
389-
}
390-
if _, ok := repo.tags[tag]; ok {
391-
repo.t.Fatalf("tag %q already exists on repo %q", commit, repo.name)
392-
}
393-
repo.tags[tag] = commit
407+
repo.runGit("tag", tag, commit)
394408
}
395409

396-
// GetRepoContent returns the content of repo based on the value of commit:
397-
// - commit is "master": return content of the most recent revision
398-
// - commit is tag: return content of the repo associating with the commit that the tag maps to
399-
// - commit is neither "master" or tag: return content of the repo associated with that commit
400-
func (repo *FakeRepo) GetRepoContent(commit string) (map[string]string, error) {
401-
rev := commit
402-
if commit == "master" {
403-
l := len(repo.history)
404-
if l == 0 {
405-
return nil, fmt.Errorf("repo %v history is empty", repo.name)
406-
}
407-
rev = repo.history[l-1]
408-
} else if val, ok := repo.tags[commit]; ok {
409-
rev = val
410-
}
411-
return repo.content[rev], nil
410+
func (repo *FakeRepo) Branch(branch, commit string) {
411+
repo.runGit("branch", branch, commit)
412+
}
413+
414+
func (repo *FakeRepo) ReadFile(commit, file string) ([]byte, error) {
415+
return repo.dir.RunCommand(context.Background(), "show", commit+":"+file)
412416
}
413417

414418
var _ GerritClient = (*FakeGerrit)(nil)
@@ -434,55 +438,42 @@ func (g *FakeGerrit) ReadBranchHead(ctx context.Context, project, branch string)
434438
if err != nil {
435439
return "", err
436440
}
437-
return repo.history[len(repo.history)-1], nil
441+
out, err := repo.dir.RunCommand(ctx, "rev-parse", "refs/heads/"+branch)
442+
return strings.TrimSpace(string(out)), err
438443
}
439444

440445
func (g *FakeGerrit) ReadFile(ctx context.Context, project string, commit string, file string) ([]byte, error) {
441446
repo, err := g.repo(project)
442447
if err != nil {
443448
return nil, err
444449
}
445-
repoContent, err := repo.GetRepoContent(commit)
446-
if err != nil {
447-
return nil, err
448-
}
449-
fileContent := repoContent[file]
450-
if fileContent == "" {
451-
return nil, fmt.Errorf("commit/file not found %v at %v: %w", file, commit, gerrit.ErrResourceNotExist)
452-
}
453-
return []byte(fileContent), nil
450+
return repo.ReadFile(commit, file)
454451
}
455452

456453
func (g *FakeGerrit) ListTags(ctx context.Context, project string) ([]string, error) {
457454
repo, err := g.repo(project)
458455
if err != nil {
459456
return nil, err
460457
}
461-
var tags []string
462-
for k := range repo.tags {
463-
tags = append(tags, k)
464-
}
465-
return tags, nil
458+
out, err := repo.dir.RunCommand(ctx, "tag", "-l")
459+
return strings.Split(strings.TrimSpace(string(out)), "\n"), err
466460
}
467461

468462
func (g *FakeGerrit) GetTag(ctx context.Context, project string, tag string) (gerrit.TagInfo, error) {
469463
repo, err := g.repo(project)
470464
if err != nil {
471465
return gerrit.TagInfo{}, err
472466
}
473-
if commit, ok := repo.tags[tag]; ok {
474-
return gerrit.TagInfo{Revision: commit}, nil
475-
} else {
476-
return gerrit.TagInfo{}, fmt.Errorf("tag not found: %w", gerrit.ErrResourceNotExist)
477-
}
467+
out, err := repo.dir.RunCommand(ctx, "rev-parse", "refs/tags/"+tag)
468+
return gerrit.TagInfo{Revision: strings.TrimSpace(string(out))}, err
478469
}
479470

480471
func (g *FakeGerrit) CreateAutoSubmitChange(_ *wf.TaskContext, input gerrit.ChangeInput, reviewers []string, contents map[string]string) (string, error) {
481472
repo, err := g.repo(input.Project)
482473
if err != nil {
483474
return "", err
484475
}
485-
commit := repo.Commit(contents)
476+
commit := repo.CommitOnBranch(input.Branch, contents)
486477
return "cl_" + commit, nil
487478
}
488479

@@ -504,9 +495,23 @@ func (g *FakeGerrit) GetCommitsInRefs(ctx context.Context, project string, commi
504495
if err != nil {
505496
return nil, err
506497
}
498+
refSet := map[string]bool{}
499+
for _, ref := range refs {
500+
refSet[ref] = true
501+
}
502+
507503
result := map[string][]string{}
508-
for _, commit := range repo.history {
509-
result[commit] = []string{"master"}
504+
for _, commit := range commits {
505+
out, err := repo.dir.RunCommand(ctx, "branch", "--format=%(refname)", "--contains="+commit)
506+
if err != nil {
507+
return nil, err
508+
}
509+
for _, branch := range strings.Split(strings.TrimSpace(string(out)), "\n") {
510+
branch := strings.TrimSpace(branch)
511+
if refSet[branch] {
512+
result[commit] = append(result[commit], branch)
513+
}
514+
}
510515
}
511516
return result, nil
512517
}
@@ -527,12 +532,12 @@ func (g *FakeGerrit) serveHTTP(w http.ResponseWriter, r *http.Request) {
527532
return
528533
}
529534
rev := strings.TrimSuffix(parts[3], ".tar.gz")
530-
repoContent, err := repo.GetRepoContent(rev)
535+
archive, err := repo.dir.RunCommand(r.Context(), "archive", "--format=tgz", rev)
531536
if err != nil {
532537
w.WriteHeader(http.StatusInternalServerError)
533538
return
534539
}
535-
ServeTarball("", repoContent, w, r)
540+
http.ServeContent(w, r, parts[3], time.Now(), bytes.NewReader(archive))
536541
}
537542

538543
func (*FakeGerrit) QueryChanges(_ context.Context, query string) ([]*gerrit.ChangeInfo, error) {

internal/task/git.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package task
6+
7+
import (
8+
"bytes"
9+
"context"
10+
"fmt"
11+
"io"
12+
"os"
13+
"os/exec"
14+
15+
"golang.org/x/oauth2"
16+
)
17+
18+
// A Git manages a set of Git repositories.
19+
type Git struct {
20+
ts oauth2.TokenSource
21+
cookieFile string
22+
}
23+
24+
// UseOAuth2Auth configures Git authentication using ts.
25+
func (g *Git) UseOAuth2Auth(ts oauth2.TokenSource) error {
26+
g.ts = ts
27+
f, err := os.CreateTemp("", "gitcookies")
28+
if err != nil {
29+
return err
30+
}
31+
g.cookieFile = f.Name()
32+
return f.Close()
33+
}
34+
35+
// Clone checks out the repository at origin into a temporary directory owned
36+
// by the resulting GitDir.
37+
func (g *Git) Clone(ctx context.Context, origin string) (*GitDir, error) {
38+
dir, err := os.MkdirTemp("", "relui-git-clone-*")
39+
if err != nil {
40+
return nil, err
41+
}
42+
if _, err := g.run(ctx, "", "clone", origin, dir); err != nil {
43+
return nil, err
44+
}
45+
return &GitDir{g, dir}, err
46+
}
47+
48+
func (g *Git) run(ctx context.Context, dir string, args ...string) ([]byte, error) {
49+
stdout := &bytes.Buffer{}
50+
stderr := &bytes.Buffer{}
51+
if err := g.runGitStreamed(ctx, stdout, stderr, dir, args...); err != nil {
52+
return stdout.Bytes(), fmt.Errorf("git command failed: %v, stderr %v", err, stderr.String())
53+
}
54+
return stdout.Bytes(), nil
55+
}
56+
57+
func (g *Git) runGitStreamed(ctx context.Context, stdout, stderr io.Writer, dir string, args ...string) error {
58+
if g.ts != nil {
59+
tok, err := g.ts.Token()
60+
if err != nil {
61+
return err
62+
}
63+
// https://github.com/curl/curl/blob/master/docs/HTTP-COOKIES.md
64+
cookieLine := fmt.Sprintf(".googlesource.com TRUE / TRUE %v o %v\n", tok.Expiry.Unix(), tok.AccessToken)
65+
if err := os.WriteFile(g.cookieFile, []byte(cookieLine), 0o700); err != nil {
66+
return fmt.Errorf("error writing git cookies: %v", err)
67+
}
68+
args = append([]string{"-c", "http.cookiefile=" + g.cookieFile}, args...)
69+
}
70+
71+
cmd := exec.CommandContext(ctx, "git", args...)
72+
cmd.Dir = dir
73+
cmd.Stdout = stdout
74+
cmd.Stderr = stderr
75+
return cmd.Run()
76+
}
77+
78+
// A GitDir is a single Git repository.
79+
type GitDir struct {
80+
git *Git
81+
dir string
82+
}
83+
84+
// RunCommand runs a Git command, returning its stdout if it succeeds, or an
85+
// error containing its stderr if it fails.
86+
func (g *GitDir) RunCommand(ctx context.Context, args ...string) ([]byte, error) {
87+
return g.git.run(ctx, g.dir, args...)
88+
}
89+
90+
// Close cleans up the repository.
91+
func (g *GitDir) Close() error {
92+
return os.RemoveAll(g.dir)
93+
}

internal/task/tagx_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ func newTagXTestDeps(t *testing.T, repos ...*FakeRepo) *tagXTestDeps {
389389
st := &types.BuildStatus{
390390
Builders: builders,
391391
}
392-
for _, commit := range r.history {
392+
for _, commit := range r.History() {
393393
st.Revisions = append(st.Revisions, types.BuildRevision{
394394
Repo: r.name,
395395
Revision: commit,

0 commit comments

Comments
 (0)