Skip to content

Commit a609c0b

Browse files
committed
git: refactor into interface
Convert the standalone functions in 'git.go' into a 'GitHelper' interface. Like 'CronScheduler' & 'CronHelper', 'GitHelper's use of 'CommandExecutor' sets it up to be unit testable in the future. Signed-off-by: Victoria Dye <[email protected]>
1 parent faab5fd commit a609c0b

File tree

4 files changed

+90
-73
lines changed

4 files changed

+90
-73
lines changed

cmd/git-bundle-server/init.go

+3-16
Original file line numberDiff line numberDiff line change
@@ -43,33 +43,20 @@ func (i *initCmd) Run(ctx context.Context, args []string) error {
4343

4444
repoProvider := utils.GetDependency[core.RepositoryProvider](ctx, i.container)
4545
bundleProvider := utils.GetDependency[bundles.BundleProvider](ctx, i.container)
46+
gitHelper := utils.GetDependency[git.GitHelper](ctx, i.container)
4647

4748
repo, err := repoProvider.CreateRepository(ctx, *route)
4849
if err != nil {
4950
return i.logger.Error(ctx, err)
5051
}
5152

5253
fmt.Printf("Cloning repository from %s\n", *url)
53-
gitErr := git.GitCommand("clone", "--bare", *url, repo.RepoDir)
54-
55-
if gitErr != nil {
56-
return i.logger.Errorf(ctx, "failed to clone repository: %w", gitErr)
57-
}
58-
59-
gitErr = git.GitCommand("-C", repo.RepoDir, "config", "remote.origin.fetch", "+refs/heads/*:refs/heads/*")
60-
if gitErr != nil {
61-
return i.logger.Errorf(ctx, "failed to configure refspec: %w", gitErr)
62-
}
63-
64-
gitErr = git.GitCommand("-C", repo.RepoDir, "fetch", "origin")
65-
if gitErr != nil {
66-
return i.logger.Errorf(ctx, "failed to fetch latest refs: %w", gitErr)
67-
}
54+
gitHelper.CloneBareRepo(ctx, *url, repo.RepoDir)
6855

6956
bundle := bundleProvider.CreateInitialBundle(ctx, repo)
7057
fmt.Printf("Constructing base bundle file at %s\n", bundle.Filename)
7158

72-
written, gitErr := git.CreateBundle(repo.RepoDir, bundle.Filename)
59+
written, gitErr := gitHelper.CreateBundle(ctx, repo.RepoDir, bundle.Filename)
7360
if gitErr != nil {
7461
return i.logger.Errorf(ctx, "failed to create bundle: %w", gitErr)
7562
}

cmd/utils/container-helpers.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/github/git-bundle-server/internal/common"
99
"github.com/github/git-bundle-server/internal/core"
1010
"github.com/github/git-bundle-server/internal/daemon"
11+
"github.com/github/git-bundle-server/internal/git"
1112
"github.com/github/git-bundle-server/internal/log"
1213
)
1314

@@ -30,7 +31,10 @@ func BuildGitBundleServerContainer(logger log.TraceLogger) *DependencyContainer
3031
)
3132
})
3233
registerDependency(container, func(ctx context.Context) bundles.BundleProvider {
33-
return bundles.NewBundleProvider(logger)
34+
return bundles.NewBundleProvider(
35+
logger,
36+
GetDependency[git.GitHelper](ctx, container),
37+
)
3438
})
3539
registerDependency(container, func(ctx context.Context) core.CronScheduler {
3640
return core.NewCronScheduler(
@@ -47,6 +51,12 @@ func BuildGitBundleServerContainer(logger log.TraceLogger) *DependencyContainer
4751
GetDependency[core.CronScheduler](ctx, container),
4852
)
4953
})
54+
registerDependency(container, func(ctx context.Context) git.GitHelper {
55+
return git.NewGitHelper(
56+
logger,
57+
GetDependency[cmd.CommandExecutor](ctx, container),
58+
)
59+
})
5060
registerDependency(container, func(ctx context.Context) daemon.DaemonProvider {
5161
t, err := daemon.NewDaemonProvider(
5262
logger,

internal/bundles/bundles.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,17 @@ type BundleProvider interface {
6565
}
6666

6767
type bundleProvider struct {
68-
logger log.TraceLogger
68+
logger log.TraceLogger
69+
gitHelper git.GitHelper
6970
}
7071

71-
func NewBundleProvider(logger log.TraceLogger) BundleProvider {
72+
func NewBundleProvider(
73+
l log.TraceLogger,
74+
g git.GitHelper,
75+
) BundleProvider {
7276
return &bundleProvider{
73-
logger: logger,
77+
logger: l,
78+
gitHelper: g,
7479
}
7580
}
7681

@@ -301,7 +306,7 @@ func (b *bundleProvider) CreateIncrementalBundle(ctx context.Context, repo *core
301306
return nil, err
302307
}
303308

304-
written, err := git.CreateIncrementalBundle(repo.RepoDir, bundle.Filename, lines)
309+
written, err := b.gitHelper.CreateIncrementalBundle(ctx, repo.RepoDir, bundle.Filename, lines)
305310
if err != nil {
306311
return nil, fmt.Errorf("failed to create incremental bundle: %w", err)
307312
}
@@ -366,7 +371,7 @@ func (b *bundleProvider) CollapseList(ctx context.Context, repo *core.Repository
366371
URI: fmt.Sprintf("./base-%d.bundle", maxTimestamp),
367372
}
368373

369-
err := git.CreateBundleFromRefs(repo.RepoDir, bundle.Filename, refs)
374+
err := b.gitHelper.CreateBundleFromRefs(ctx, repo.RepoDir, bundle.Filename, refs)
370375
if err != nil {
371376
return err
372377
}

internal/git/git.go

+66-51
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,73 @@ package git
22

33
import (
44
"bytes"
5+
"context"
56
"fmt"
67
"os"
7-
"os/exec"
88
"strings"
9-
)
109

11-
func GetExecCommand(args []string) (*exec.Cmd, error) {
12-
git, err := exec.LookPath("git")
13-
if err != nil {
14-
return nil, fmt.Errorf("failed to find 'git' on the path: %w", err)
15-
}
10+
"github.com/github/git-bundle-server/internal/cmd"
11+
"github.com/github/git-bundle-server/internal/log"
12+
)
1613

17-
cmd := exec.Command(git, args...)
18-
cmd.Env = append(cmd.Env, "LC_CTYPE=C")
14+
type GitHelper interface {
15+
CreateBundle(ctx context.Context, repoDir string, filename string) (bool, error)
16+
CreateBundleFromRefs(ctx context.Context, repoDir string, filename string, refs map[string]string) error
17+
CreateIncrementalBundle(ctx context.Context, repoDir string, filename string, prereqs []string) (bool, error)
18+
CloneBareRepo(ctx context.Context, url string, destination string) error
19+
}
1920

20-
return cmd, nil
21+
type gitHelper struct {
22+
logger log.TraceLogger
23+
cmdExec cmd.CommandExecutor
2124
}
2225

23-
func GitCommand(args ...string) error {
24-
cmd, err := GetExecCommand(args)
25-
if err != nil {
26-
return err
26+
func NewGitHelper(l log.TraceLogger, c cmd.CommandExecutor) GitHelper {
27+
return &gitHelper{
28+
logger: l,
29+
cmdExec: c,
2730
}
31+
}
2832

29-
cmd.Stderr = os.Stderr
30-
cmd.Stdout = os.Stdout
31-
32-
err = cmd.Start()
33-
if err != nil {
34-
return fmt.Errorf("git command failed to start: %w", err)
35-
}
33+
func (g *gitHelper) gitCommand(ctx context.Context, args ...string) error {
34+
exitCode, err := g.cmdExec.Run(ctx, "git", args,
35+
cmd.Stdout(os.Stdout),
36+
cmd.Stderr(os.Stderr),
37+
cmd.Env([]string{"LC_CTYPE=C"}),
38+
)
3639

37-
err = cmd.Wait()
3840
if err != nil {
39-
return fmt.Errorf("git command returned a failure: %w", err)
41+
return g.logger.Error(ctx, err)
42+
} else if exitCode != 0 {
43+
return g.logger.Errorf(ctx, "'git' exited with status %d", exitCode)
4044
}
4145

42-
return err
46+
return nil
4347
}
4448

45-
func GitCommandWithStdin(stdinLines []string, args ...string) error {
46-
cmd, err := GetExecCommand(args)
47-
if err != nil {
48-
return err
49-
}
50-
49+
func (g *gitHelper) gitCommandWithStdin(ctx context.Context, stdinLines []string, args ...string) error {
5150
buffer := bytes.Buffer{}
5251
for line := range stdinLines {
5352
buffer.Write([]byte(stdinLines[line] + "\n"))
5453
}
54+
exitCode, err := g.cmdExec.Run(ctx, "git", args,
55+
cmd.Stdin(&buffer),
56+
cmd.Stdout(os.Stdout),
57+
cmd.Stderr(os.Stderr),
58+
cmd.Env([]string{"LC_CTYPE=C"}),
59+
)
5560

56-
cmd.Stdin = &buffer
57-
58-
errorBuffer := bytes.Buffer{}
59-
cmd.Stderr = &errorBuffer
60-
cmd.Stdout = os.Stdout
61-
62-
err = cmd.Start()
63-
if err != nil {
64-
return fmt.Errorf("git command failed to start: %w", err)
65-
}
66-
67-
err = cmd.Wait()
6861
if err != nil {
69-
return fmt.Errorf("git command returned a failure: %w\nstderr: %s", err, errorBuffer.String())
62+
return g.logger.Error(ctx, err)
63+
} else if exitCode != 0 {
64+
return g.logger.Errorf(ctx, "'git' exited with status %d", exitCode)
7065
}
7166

72-
return err
67+
return nil
7368
}
7469

75-
func CreateBundle(repoDir string, filename string) (bool, error) {
76-
err := GitCommand(
70+
func (g *gitHelper) CreateBundle(ctx context.Context, repoDir string, filename string) (bool, error) {
71+
err := g.gitCommand(ctx,
7772
"-C", repoDir, "bundle", "create",
7873
filename, "--branches")
7974
if err != nil {
@@ -86,19 +81,19 @@ func CreateBundle(repoDir string, filename string) (bool, error) {
8681
return true, nil
8782
}
8883

89-
func CreateBundleFromRefs(repoDir string, filename string, refs map[string]string) error {
84+
func (g *gitHelper) CreateBundleFromRefs(ctx context.Context, repoDir string, filename string, refs map[string]string) error {
9085
refNames := []string{}
9186

9287
for ref, oid := range refs {
93-
err := GitCommand("-C", repoDir, "branch", "-f", ref, oid)
88+
err := g.gitCommand(ctx, "-C", repoDir, "branch", "-f", ref, oid)
9489
if err != nil {
9590
return fmt.Errorf("failed to create ref %s: %w", ref, err)
9691
}
9792

9893
refNames = append(refNames, ref)
9994
}
10095

101-
err := GitCommandWithStdin(
96+
err := g.gitCommandWithStdin(ctx,
10297
refNames,
10398
"-C", repoDir, "bundle", "create",
10499
filename, "--stdin")
@@ -109,8 +104,8 @@ func CreateBundleFromRefs(repoDir string, filename string, refs map[string]strin
109104
return nil
110105
}
111106

112-
func CreateIncrementalBundle(repoDir string, filename string, prereqs []string) (bool, error) {
113-
err := GitCommandWithStdin(
107+
func (g *gitHelper) CreateIncrementalBundle(ctx context.Context, repoDir string, filename string, prereqs []string) (bool, error) {
108+
err := g.gitCommandWithStdin(ctx,
114109
prereqs, "-C", repoDir, "bundle", "create",
115110
filename, "--stdin", "--branches")
116111
if err != nil {
@@ -122,3 +117,23 @@ func CreateIncrementalBundle(repoDir string, filename string, prereqs []string)
122117

123118
return true, nil
124119
}
120+
121+
func (g *gitHelper) CloneBareRepo(ctx context.Context, url string, destination string) error {
122+
gitErr := g.gitCommand(ctx, "clone", "--bare", url, destination)
123+
124+
if gitErr != nil {
125+
return g.logger.Errorf(ctx, "failed to clone repository: %w", gitErr)
126+
}
127+
128+
gitErr = g.gitCommand(ctx, "-C", destination, "config", "remote.origin.fetch", "+refs/heads/*:refs/heads/*")
129+
if gitErr != nil {
130+
return g.logger.Errorf(ctx, "failed to configure refspec: %w", gitErr)
131+
}
132+
133+
gitErr = g.gitCommand(ctx, "-C", destination, "fetch", "origin")
134+
if gitErr != nil {
135+
return g.logger.Errorf(ctx, "failed to fetch latest refs: %w", gitErr)
136+
}
137+
138+
return nil
139+
}

0 commit comments

Comments
 (0)