1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
|
package gittest
import (
"bytes"
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitaly/v16/internal/git"
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config"
"gitlab.com/gitlab-org/gitaly/v16/internal/helper/perm"
"gitlab.com/gitlab-org/gitaly/v16/internal/helper/text"
"gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
"google.golang.org/protobuf/types/known/timestamppb"
)
var (
// DefaultCommitterName is the default name of the committer and author used to create
// commits.
DefaultCommitterName = "Scrooge McDuck"
// DefaultCommitterMail is the default mail of the committer and author used to create
// commits.
DefaultCommitterMail = "scrooge@mcduck.com"
// DefaultCommitTime is the default time used as written by WriteCommit().
DefaultCommitTime = time.Date(2019, 11, 3, 11, 27, 59, 0, time.FixedZone("", 60*60))
// DefaultCommitterSignature is the default signature in the format like it would be present
// in commits: "$name <$email> $unixtimestamp $timezone".
DefaultCommitterSignature = fmt.Sprintf(
"%s <%s> %d %s", DefaultCommitterName, DefaultCommitterMail, DefaultCommitTime.Unix(), DefaultCommitTime.Format("-0700"),
)
// DefaultCommitAuthor is the Protobuf message representation of the default committer and
// author used to create commits.
DefaultCommitAuthor = &gitalypb.CommitAuthor{
Name: []byte(DefaultCommitterName),
Email: []byte(DefaultCommitterMail),
Date: timestamppb.New(DefaultCommitTime),
Timezone: []byte("+0100"),
}
)
type writeCommitConfig struct {
reference string
parents []git.ObjectID
authorDate time.Time
authorName string
committerName string
committerDate time.Time
message string
treeEntries []TreeEntry
treeID git.ObjectID
alternateObjectDir string
}
// WriteCommitOption is an option which can be passed to WriteCommit.
type WriteCommitOption func(*writeCommitConfig)
// WithReference is an option for WriteCommit which will cause it to update the given reference to
// point to the new commit. This function requires the fully-qualified reference name.
func WithReference(reference string) WriteCommitOption {
return func(cfg *writeCommitConfig) {
cfg.reference = reference
}
}
// WithBranch is an option for WriteCommit which will cause it to update the given branch name to
// the new commit.
func WithBranch(branch string) WriteCommitOption {
return WithReference("refs/heads/" + branch)
}
// WithMessage is an option for WriteCommit which will set the commit message.
func WithMessage(message string) WriteCommitOption {
return func(cfg *writeCommitConfig) {
cfg.message = message
}
}
// WithParents is an option for WriteCommit which will set the parent OIDs of the resulting commit.
func WithParents(parents ...git.ObjectID) WriteCommitOption {
return func(cfg *writeCommitConfig) {
if parents != nil {
cfg.parents = parents
} else {
// We're explicitly initializing parents here such that we can discern the
// case where the commit should be created with no parents.
cfg.parents = []git.ObjectID{}
}
}
}
// WithTreeEntries is an option for WriteCommit which will cause it to create a new tree and use it
// as root tree of the resulting commit.
func WithTreeEntries(entries ...TreeEntry) WriteCommitOption {
return func(cfg *writeCommitConfig) {
cfg.treeEntries = entries
}
}
// WithTree is an option for WriteCommit which will cause it to use the given object ID as the root
// tree of the resulting commit.
// as root tree of the resulting commit.
func WithTree(treeID git.ObjectID) WriteCommitOption {
return func(cfg *writeCommitConfig) {
cfg.treeID = treeID
}
}
// WithAuthorName is an option for WriteCommit which will set the author name.
func WithAuthorName(name string) WriteCommitOption {
return func(cfg *writeCommitConfig) {
cfg.authorName = name
}
}
// WithAuthorDate is an option for WriteCommit which will set the author date.
func WithAuthorDate(date time.Time) WriteCommitOption {
return func(cfg *writeCommitConfig) {
cfg.authorDate = date
}
}
// WithCommitterName is an option for WriteCommit which will set the committer name.
func WithCommitterName(name string) WriteCommitOption {
return func(cfg *writeCommitConfig) {
cfg.committerName = name
}
}
// WithCommitterDate is an option for WriteCommit which will set the committer date.
func WithCommitterDate(date time.Time) WriteCommitOption {
return func(cfg *writeCommitConfig) {
cfg.committerDate = date
}
}
// WithAlternateObjectDirectory will cause the commit to be written into the given alternate object
// directory. This can either be an absolute path or a relative path. In the latter case the path
// is considered to be relative to the repository path.
func WithAlternateObjectDirectory(alternateObjectDir string) WriteCommitOption {
return func(cfg *writeCommitConfig) {
cfg.alternateObjectDir = alternateObjectDir
}
}
// WriteCommit writes a new commit into the target repository.
func WriteCommit(tb testing.TB, cfg config.Cfg, repoPath string, opts ...WriteCommitOption) git.ObjectID {
tb.Helper()
var writeCommitConfig writeCommitConfig
for _, opt := range opts {
opt(&writeCommitConfig)
}
message := "message"
if writeCommitConfig.message != "" {
message = writeCommitConfig.message
}
stdin := bytes.NewBufferString(message)
if len(writeCommitConfig.treeEntries) > 0 && writeCommitConfig.treeID != "" {
require.FailNow(tb, "cannot set tree entries and tree ID at the same time")
}
var tree string
if writeCommitConfig.treeEntries != nil {
tree = WriteTree(tb, cfg, repoPath, writeCommitConfig.treeEntries).String()
} else if writeCommitConfig.treeID != "" {
tree = writeCommitConfig.treeID.String()
} else if len(writeCommitConfig.parents) == 0 {
tree = WriteTree(tb, cfg, repoPath, []TreeEntry{}).String()
} else {
tree = writeCommitConfig.parents[0].String() + "^{tree}"
}
if writeCommitConfig.authorName == "" {
writeCommitConfig.authorName = DefaultCommitterName
}
if writeCommitConfig.authorDate.IsZero() {
writeCommitConfig.authorDate = time.Date(2019, 11, 3, 11, 27, 59, 0, time.FixedZone("UTC+1", 1*60*60))
}
if writeCommitConfig.committerName == "" {
writeCommitConfig.committerName = DefaultCommitterName
}
if writeCommitConfig.committerDate.IsZero() {
writeCommitConfig.committerDate = time.Date(2019, 11, 3, 11, 27, 59, 0, time.FixedZone("UTC+1", 1*60*60))
}
// Use 'commit-tree' instead of 'commit' because we are in a bare
// repository. What we do here is the same as "commit -m message
// --allow-empty".
commitArgs := []string{
"-c", fmt.Sprintf("user.name=%s", writeCommitConfig.committerName),
"-c", fmt.Sprintf("user.email=%s", DefaultCommitterMail),
"-C", repoPath,
"commit-tree", "-F", "-", tree,
}
var env []string
if writeCommitConfig.alternateObjectDir != "" {
require.True(tb, filepath.IsAbs(writeCommitConfig.alternateObjectDir),
"alternate object directory must be an absolute path")
require.NoError(tb, os.MkdirAll(writeCommitConfig.alternateObjectDir, perm.SharedDir))
env = append(env,
fmt.Sprintf("GIT_OBJECT_DIRECTORY=%s", writeCommitConfig.alternateObjectDir),
fmt.Sprintf("GIT_ALTERNATE_OBJECT_DIRECTORIES=%s", filepath.Join(repoPath, "objects")),
)
}
env = append(env,
fmt.Sprintf("GIT_AUTHOR_DATE=%d %s", writeCommitConfig.authorDate.Unix(), writeCommitConfig.authorDate.Format("-0700")),
fmt.Sprintf("GIT_AUTHOR_NAME=%s", writeCommitConfig.authorName),
fmt.Sprintf("GIT_AUTHOR_EMAIL=%s", DefaultCommitterMail),
fmt.Sprintf("GIT_COMMITTER_DATE=%d %s", writeCommitConfig.committerDate.Unix(), writeCommitConfig.committerDate.Format("-0700")),
fmt.Sprintf("GIT_COMMITTER_NAME=%s", writeCommitConfig.committerName),
fmt.Sprintf("GIT_COMMITTER_EMAIL=%s", DefaultCommitterMail),
)
for _, parent := range writeCommitConfig.parents {
commitArgs = append(commitArgs, "-p", parent.String())
}
stdout := ExecOpts(tb, cfg, ExecConfig{
Stdin: stdin,
Env: env,
}, commitArgs...)
oid, err := DefaultObjectHash.FromHex(text.ChompBytes(stdout))
require.NoError(tb, err)
if writeCommitConfig.reference != "" {
ExecOpts(tb, cfg, ExecConfig{
Env: env,
}, "-C", repoPath, "update-ref", writeCommitConfig.reference, oid.String())
}
return oid
}
func authorEqualIgnoringDate(tb testing.TB, expected *gitalypb.CommitAuthor, actual *gitalypb.CommitAuthor) {
tb.Helper()
require.Equal(tb, expected.GetName(), actual.GetName(), "author name does not match")
require.Equal(tb, expected.GetEmail(), actual.GetEmail(), "author mail does not match")
}
// CommitEqual tests if two `GitCommit`s are equal
func CommitEqual(tb testing.TB, expected, actual *gitalypb.GitCommit) {
tb.Helper()
authorEqualIgnoringDate(tb, expected.GetAuthor(), actual.GetAuthor())
authorEqualIgnoringDate(tb, expected.GetCommitter(), actual.GetCommitter())
require.Equal(tb, expected.GetBody(), actual.GetBody(), "body does not match")
require.Equal(tb, expected.GetSubject(), actual.GetSubject(), "subject does not match")
require.Equal(tb, expected.GetId(), actual.GetId(), "object ID does not match")
require.Equal(tb, expected.GetParentIds(), actual.GetParentIds(), "parent IDs do not match")
}
|