1// +build static,system_libgit2
2
3package commit
4
5import (
6	"context"
7	"encoding/gob"
8	"errors"
9	"fmt"
10	"io"
11
12	git "github.com/libgit2/git2go/v31"
13	"gitlab.com/gitlab-org/gitaly/v14/internal/git2go"
14)
15
16// Run runs the commit subcommand.
17func Run(ctx context.Context, stdin io.Reader, stdout io.Writer) error {
18	var params git2go.CommitParams
19	if err := gob.NewDecoder(stdin).Decode(&params); err != nil {
20		return err
21	}
22
23	commitID, err := commit(ctx, params)
24	return gob.NewEncoder(stdout).Encode(git2go.Result{
25		CommitID: commitID,
26		Error:    git2go.SerializableError(err),
27	})
28}
29
30func commit(ctx context.Context, params git2go.CommitParams) (string, error) {
31	repo, err := git.OpenRepository(params.Repository)
32	if err != nil {
33		return "", fmt.Errorf("open repository: %w", err)
34	}
35
36	index, err := git.NewIndex()
37	if err != nil {
38		return "", fmt.Errorf("new index: %w", err)
39	}
40
41	var parents []*git.Oid
42	if params.Parent != "" {
43		parentOID, err := git.NewOid(params.Parent)
44		if err != nil {
45			return "", fmt.Errorf("parse base commit oid: %w", err)
46		}
47
48		parents = []*git.Oid{parentOID}
49
50		baseCommit, err := repo.LookupCommit(parentOID)
51		if err != nil {
52			return "", fmt.Errorf("lookup commit: %w", err)
53		}
54
55		baseTree, err := baseCommit.Tree()
56		if err != nil {
57			return "", fmt.Errorf("lookup tree: %w", err)
58		}
59
60		if err := index.ReadTree(baseTree); err != nil {
61			return "", fmt.Errorf("read tree: %w", err)
62		}
63	}
64
65	for _, action := range params.Actions {
66		if err := apply(action, repo, index); err != nil {
67			if git.IsErrorClass(err, git.ErrClassIndex) {
68				err = git2go.IndexError(err.Error())
69			}
70
71			return "", fmt.Errorf("apply action %T: %w", action, err)
72		}
73	}
74
75	treeOID, err := index.WriteTreeTo(repo)
76	if err != nil {
77		return "", fmt.Errorf("write tree: %w", err)
78	}
79
80	author := git.Signature(params.Author)
81	committer := git.Signature(params.Committer)
82	commitID, err := repo.CreateCommitFromIds("", &author, &committer, params.Message, treeOID, parents...)
83	if err != nil {
84		if git.IsErrorClass(err, git.ErrClassInvalid) {
85			return "", git2go.InvalidArgumentError(err.Error())
86		}
87
88		return "", fmt.Errorf("create commit: %w", err)
89	}
90
91	return commitID.String(), nil
92}
93
94func apply(action git2go.Action, repo *git.Repository, index *git.Index) error {
95	switch action := action.(type) {
96	case git2go.ChangeFileMode:
97		return applyChangeFileMode(action, index)
98	case git2go.CreateDirectory:
99		return applyCreateDirectory(action, repo, index)
100	case git2go.CreateFile:
101		return applyCreateFile(action, index)
102	case git2go.DeleteFile:
103		return applyDeleteFile(action, index)
104	case git2go.MoveFile:
105		return applyMoveFile(action, index)
106	case git2go.UpdateFile:
107		return applyUpdateFile(action, index)
108	default:
109		return errors.New("unsupported action")
110	}
111}
112