1package git2go
2
3import (
4	"bytes"
5	"context"
6	"encoding/gob"
7	"errors"
8	"fmt"
9	"io"
10	"os/exec"
11	"path/filepath"
12
13	"gitlab.com/gitlab-org/gitaly/v14/internal/command"
14	"gitlab.com/gitlab-org/gitaly/v14/internal/git"
15	"gitlab.com/gitlab-org/gitaly/v14/internal/git/alternates"
16	"gitlab.com/gitlab-org/gitaly/v14/internal/git/repository"
17	"gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config"
18	"gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/storage"
19	"gitlab.com/gitlab-org/gitaly/v14/internal/version"
20)
21
22var (
23	// ErrInvalidArgument is returned in case the merge arguments are invalid.
24	ErrInvalidArgument = errors.New("invalid parameters")
25
26	// BinaryName is a binary name with version suffix .
27	BinaryName = "gitaly-git2go-" + version.GetModuleVersion()
28)
29
30// Executor executes gitaly-git2go.
31type Executor struct {
32	binaryPath    string
33	gitBinaryPath string
34	locator       storage.Locator
35}
36
37// NewExecutor returns a new gitaly-git2go executor using binaries as configured in the given
38// configuration.
39func NewExecutor(cfg config.Cfg, locator storage.Locator) Executor {
40	return Executor{
41		binaryPath:    filepath.Join(cfg.BinDir, BinaryName),
42		gitBinaryPath: cfg.Git.BinPath,
43		locator:       locator,
44	}
45}
46
47func (b Executor) run(ctx context.Context, repo repository.GitRepo, stdin io.Reader, args ...string) (*bytes.Buffer, error) {
48	repoPath, err := b.locator.GetRepoPath(repo)
49	if err != nil {
50		return nil, fmt.Errorf("gitaly-git2go: %w", err)
51	}
52
53	env := alternates.Env(repoPath, repo.GetGitObjectDirectory(), repo.GetGitAlternateObjectDirectories())
54
55	var stderr, stdout bytes.Buffer
56	cmd, err := command.New(ctx, exec.Command(b.binaryPath, args...), stdin, &stdout, &stderr, env...)
57	if err != nil {
58		return nil, err
59	}
60
61	if err := cmd.Wait(); err != nil {
62		if _, ok := err.(*exec.ExitError); ok {
63			return nil, fmt.Errorf("%s", stderr.String())
64		}
65		return nil, err
66	}
67
68	return &stdout, nil
69}
70
71// runWithGob runs the specified gitaly-git2go cmd with the request gob-encoded
72// as input and returns the commit ID as string or an error.
73func (b Executor) runWithGob(ctx context.Context, repo repository.GitRepo, cmd string, request interface{}) (git.ObjectID, error) {
74	input := &bytes.Buffer{}
75	if err := gob.NewEncoder(input).Encode(request); err != nil {
76		return "", fmt.Errorf("%s: %w", cmd, err)
77	}
78
79	output, err := b.run(ctx, repo, input, cmd)
80	if err != nil {
81		return "", fmt.Errorf("%s: %w", cmd, err)
82	}
83
84	var result Result
85	if err := gob.NewDecoder(output).Decode(&result); err != nil {
86		return "", fmt.Errorf("%s: %w", cmd, err)
87	}
88
89	if result.Error != nil {
90		return "", fmt.Errorf("%s: %w", cmd, result.Error)
91	}
92
93	commitID, err := git.NewObjectIDFromHex(result.CommitID)
94	if err != nil {
95		return "", fmt.Errorf("could not parse commit ID: %w", err)
96	}
97
98	return commitID, nil
99}
100