From 3236114adbeb792c156f9cb222f4f05ac69baaae Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 26 Nov 2020 16:38:52 +0100 Subject: [PATCH] Parse Git commit trailers when processing commits This adds support for parsing Git trailers (https://git-scm.com/docs/git-interpret-trailers). GitLab will use these trailers to generate changelog information, as part of the epic https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/351. Parsing is done by parsing the output of `git log` when finding multiple commits, and by parsing the output of `git show` when finding a single commit. The Go parser is written such that it allocates as few objects as possible, making it possible to efficiently parse trailers; even when listing thousands of commits. See https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1364 for more information. --- .../unreleased/parsing-commit-trailers.yml | 5 + internal/git/log/commit.go | 52 +++++ internal/git/log/commit_test.go | 25 +++ internal/git/subcommand.go | 1 + internal/git/trailerparser/trailerparser.go | 100 +++++++++ .../git/trailerparser/trailerparser_test.go | 121 ++++++++++ internal/gitaly/service/commit/find_commit.go | 2 +- .../gitaly/service/commit/find_commit_test.go | 60 ++++- .../gitaly/service/commit/find_commits.go | 11 +- .../service/commit/find_commits_test.go | 53 +++++ proto/go/gitalypb/shared.pb.go | 207 ++++++++++++------ proto/shared.proto | 14 ++ ruby/lib/gitaly_server/utils.rb | 9 +- ruby/lib/gitlab/git/commit.rb | 19 +- ruby/proto/gitaly/shared_pb.rb | 6 + ruby/spec/lib/gitlab/git/commit_spec.rb | 3 +- 16 files changed, 606 insertions(+), 82 deletions(-) create mode 100644 changelogs/unreleased/parsing-commit-trailers.yml create mode 100644 internal/git/trailerparser/trailerparser.go create mode 100644 internal/git/trailerparser/trailerparser_test.go diff --git a/changelogs/unreleased/parsing-commit-trailers.yml b/changelogs/unreleased/parsing-commit-trailers.yml new file mode 100644 index 0000000000..bbc5353876 --- /dev/null +++ b/changelogs/unreleased/parsing-commit-trailers.yml @@ -0,0 +1,5 @@ +--- +title: Parse Git commit trailers when processing commits +merge_request: 2842 +author: +type: added diff --git a/internal/git/log/commit.go b/internal/git/log/commit.go index cf25df4656..8653f0323b 100644 --- a/internal/git/log/commit.go +++ b/internal/git/log/commit.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "context" + "fmt" "io" "io/ioutil" "strconv" @@ -12,6 +13,7 @@ import ( "github.com/golang/protobuf/ptypes/timestamp" "gitlab.com/gitlab-org/gitaly/internal/git" "gitlab.com/gitlab-org/gitaly/internal/git/catfile" + "gitlab.com/gitlab-org/gitaly/internal/git/trailerparser" "gitlab.com/gitlab-org/gitaly/internal/helper" "gitlab.com/gitlab-org/gitaly/internal/storage" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" @@ -28,6 +30,17 @@ func GetCommit(ctx context.Context, locator storage.Locator, repo *gitalypb.Repo return GetCommitCatfile(ctx, c, revision) } +// GetCommitWithTrailers tries to resolve a revision to a Git commit, including +// Git trailers in its output. +func GetCommitWithTrailers(ctx context.Context, locator storage.Locator, repo *gitalypb.Repository, revision string) (*gitalypb.GitCommit, error) { + c, err := catfile.New(ctx, locator, repo) + if err != nil { + return nil, err + } + + return GetCommitCatfileWithTrailers(ctx, repo, c, revision) +} + // GetCommitCatfile looks up a commit by revision using an existing catfile.Batch instance. func GetCommitCatfile(ctx context.Context, c catfile.Batch, revision string) (*gitalypb.GitCommit, error) { obj, err := c.Commit(ctx, revision+"^{commit}") @@ -38,6 +51,45 @@ func GetCommitCatfile(ctx context.Context, c catfile.Batch, revision string) (*g return parseRawCommit(obj.Reader, &obj.ObjectInfo) } +// GetCommitCatfileWithTrailers looks up a commit by revision using an existing +// catfile.Batch instance, and includes Git trailers in the returned commit. +func GetCommitCatfileWithTrailers(ctx context.Context, repo *gitalypb.Repository, c catfile.Batch, revision string) (*gitalypb.GitCommit, error) { + commit, err := GetCommitCatfile(ctx, c, revision) + + if err != nil { + return nil, err + } + + // We use the commit ID here instead of revision. This way we still get + // trailers if the revision is not a SHA but e.g. a tag name. + showCmd, err := git.SafeCmd(ctx, repo, nil, git.SubCmd{ + Name: "show", + Args: []string{commit.Id}, + Flags: []git.Option{ + git.Flag{Name: "--format=%(trailers:unfold,separator=%x00)"}, + git.Flag{Name: "--no-patch"}, + }, + }) + + if err != nil { + return nil, fmt.Errorf("error when creating git show command: %w", err) + } + + scanner := bufio.NewScanner(showCmd) + + if scanner.Scan() { + if len(scanner.Text()) > 0 { + commit.Trailers = trailerparser.Parse([]byte(scanner.Text())) + } + + if scanner.Scan() { + return nil, fmt.Errorf("git show produced more than one line of output, the second line is: %v", scanner.Text()) + } + } + + return commit, nil +} + // GetCommitMessage looks up a commit message and returns it in its entirety. func GetCommitMessage(ctx context.Context, c catfile.Batch, repo *gitalypb.Repository, revision string) ([]byte, error) { obj, err := c.Commit(ctx, revision+"^{commit}") diff --git a/internal/git/log/commit_test.go b/internal/git/log/commit_test.go index 4fcf0777b2..6e65a5ecad 100644 --- a/internal/git/log/commit_test.go +++ b/internal/git/log/commit_test.go @@ -166,3 +166,28 @@ func TestGetCommitCatfile(t *testing.T) { }) } } + +func TestGetCommitCatfileWithTrailers(t *testing.T) { + ctx, cancel := testhelper.Context() + ctx = metadata.NewIncomingContext(ctx, metadata.MD{}) + defer cancel() + + testRepo, _, cleanup := testhelper.NewTestRepo(t) + defer cleanup() + + locator := config.NewLocator(config.Config) + catfile, err := catfile.New(ctx, locator, testRepo) + + require.NoError(t, err) + + commit, err := GetCommitCatfileWithTrailers(ctx, testRepo, catfile, "5937ac0a7beb003549fc5fd26fc247adbce4a52e") + + require.NoError(t, err) + + require.Equal(t, commit.Trailers, []*gitalypb.CommitTrailer{ + &gitalypb.CommitTrailer{ + Key: []byte("Signed-off-by"), + Value: []byte("Dmitriy Zaporozhets "), + }, + }) +} diff --git a/internal/git/subcommand.go b/internal/git/subcommand.go index 4207d7f4e3..527a04a0e8 100644 --- a/internal/git/subcommand.go +++ b/internal/git/subcommand.go @@ -47,6 +47,7 @@ var subcommands = map[string]uint{ "repack": scNoRefUpdates | scGeneratesPackfiles, "rev-list": scReadOnly, "rev-parse": scReadOnly | scNoEndOfOptions, + "show": scReadOnly, "show-ref": scReadOnly, "symbolic-ref": 0, "tag": 0, diff --git a/internal/git/trailerparser/trailerparser.go b/internal/git/trailerparser/trailerparser.go new file mode 100644 index 0000000000..867ce9e417 --- /dev/null +++ b/internal/git/trailerparser/trailerparser.go @@ -0,0 +1,100 @@ +package trailerparser + +import ( + "bytes" + + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" +) + +const ( + // The maximum number of trailers that we'll parse. + maxTrailers = 64 + + // The maximum size (in bytes) of trailer keys + // + // The limit should be sufficient enough to support multi-byte trailer keys + // (or just long trailer keys). + maxKeySize = 128 + + // The maximum size (in bytes) of trailer values. + // + // This limit should be sufficient enough to support URLs, long Email + // addresses, etc. + maxValueSize = 512 +) + +// Parse parses Git trailers into a list of CommitTrailer objects. +// +// The expected input is a single line containing trailers in the following +// format: +// +// KEY:VALUE\0KEY:VALUE +// +// Where \0 is a NULL byte. The input should not end in a NULL byte. +// +// Trailers must be separated with a null byte, as their values can include any +// other separater character. NULL bytes however are not allowed in commit +// messages, and thus can't occur in trailers. +// +// The key-value separator must be a colon, as this is the separator the Git CLI +// uses when obtaining the trailers of a commit message. +// +// Trailer keys and values are limited to a certain number of bytes. If these +// limits are reached, parsing stops and all trailers parsed until that point +// are returned. This ensures we don't continue to consume a potentially very +// large input. +// +// The limits this parser imposes on the sizes/amounts are loosely based on +// trailers found in GitLab's own repository. Here are just a few examples: +// +// Change-Id: I009c716ce2475b9efa3fd07aee9215fca7a1c150 +// Changelog: https://github.com/nahi/httpclient/blob/b51d7a8bb78f71726b08fbda5abfb900d627569f/CHANGELOG.md#changes-in-282 +// Co-Authored-By: Alex Kalderimis +// fixes: https://gitlab.com/gitlab-org/gitlab-ce/issues/44458 +// Signed-off-by: Dmitriy Zaporozhets +// See: https://gitlab.com/gitlab-org/gitlab-ee/blob/ff9ad690650c23665439499d23f3ed22103b55bb/spec/spec_helper.rb#L50 +func Parse(input []byte) []*gitalypb.CommitTrailer { + // The choice of a nil slice instead of an empty one is deliberate: gRPC + // turns empty slices into nil. + // + // If we were to use an empty array, then compare that with a commit + // retrieved using e.g. the Ruby server, we'd be comparing different types: + // the Ruby server would produce a nil for an empty list of trailers, while + // Gitaly would produce an empty slice. Using a nil slice here ensures these + // type differences don't occur. + var trailers []*gitalypb.CommitTrailer + startPos := 0 + max := len(input) + + for startPos < max && len(trailers) < maxTrailers { + endPos := bytes.IndexByte(input[startPos:], byte('\000')) + + // endPos starts as an index relative to the start position, so we need + // to convert this to an absolute index before we use it for slicing. + if endPos >= 0 { + endPos = startPos + endPos + } else { + endPos = max + } + + sepPos := bytes.IndexByte(input[startPos:endPos], byte(':')) + + if sepPos > 0 { + key := input[startPos : startPos+sepPos] + value := bytes.TrimSpace(input[startPos+sepPos+1 : endPos]) + + if len(key) > maxKeySize || len(value) > maxValueSize { + break + } + + trailers = append(trailers, &gitalypb.CommitTrailer{ + Key: key, + Value: value, + }) + } + + startPos = endPos + 1 + } + + return trailers +} diff --git a/internal/git/trailerparser/trailerparser_test.go b/internal/git/trailerparser/trailerparser_test.go new file mode 100644 index 0000000000..092b65cafe --- /dev/null +++ b/internal/git/trailerparser/trailerparser_test.go @@ -0,0 +1,121 @@ +package trailerparser + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEmptyMessage(t *testing.T) { + input := []byte{} + pairs := Parse(input) + + assert.Equal(t, 0, len(pairs)) +} + +func TestMessageWithoutTrailers(t *testing.T) { + input := []byte("") + pairs := Parse(input) + + assert.Equal(t, 0, len(pairs)) +} + +func TestSingleTrailer(t *testing.T) { + input := []byte("Key: Value") + pairs := Parse(input) + + assert.Equal(t, 1, len(pairs)) + assert.Equal(t, "Key", string(pairs[0].Key)) + assert.Equal(t, "Value", string(pairs[0].Value)) +} + +func TestSingleTrailerWithTooLongKey(t *testing.T) { + input := []byte("Foo: Bar\000") + + for i := 0; i < (maxKeySize + 1); i++ { + input = append(input, byte('a')) + } + + input = append(input, []byte(": Value")...) + + pairs := Parse(input) + + assert.Equal(t, 1, len(pairs)) + assert.Equal(t, "Foo", string(pairs[0].Key)) + assert.Equal(t, "Bar", string(pairs[0].Value)) +} + +func TestSingleTrailerWithTooLongValue(t *testing.T) { + input := []byte("Foo: Bar\000Key: ") + + for i := 0; i < (maxValueSize + 1); i++ { + input = append(input, byte('a')) + } + + pairs := Parse(input) + + assert.Equal(t, 1, len(pairs)) + assert.Equal(t, "Foo", string(pairs[0].Key)) + assert.Equal(t, "Bar", string(pairs[0].Value)) +} + +func TestTooManyTrailers(t *testing.T) { + input := []byte{} + + for i := 0; i < maxTrailers+1; i++ { + input = append(input, []byte("Key: value\000")...) + } + + pairs := Parse(input) + + assert.Equal(t, maxTrailers, len(pairs)) +} + +func TestSingleTrailerWithoutValue(t *testing.T) { + input := []byte("Key:") + pairs := Parse(input) + + assert.Equal(t, 1, len(pairs)) + assert.Equal(t, "Key", string(pairs[0].Key)) + assert.Equal(t, "", string(pairs[0].Value)) +} + +func TestSingleTrailerWithoutValueWithTrailingNullByte(t *testing.T) { + input := []byte("Key:") + pairs := Parse(input) + + assert.Equal(t, 1, len(pairs)) + assert.Equal(t, "Key", string(pairs[0].Key)) + assert.Equal(t, "", string(pairs[0].Value)) +} + +func TestMultipleTrailers(t *testing.T) { + input := []byte("Key1: Value1\000Key2: Value2") + pairs := Parse(input) + + assert.Equal(t, 2, len(pairs)) + assert.Equal(t, "Key1", string(pairs[0].Key)) + assert.Equal(t, "Value1", string(pairs[0].Value)) + assert.Equal(t, "Key2", string(pairs[1].Key)) + assert.Equal(t, "Value2", string(pairs[1].Value)) +} + +func TestMultipleTrailersWithTrailingNullByte(t *testing.T) { + input := []byte("Key1: Value1\000Key2: Value2") + pairs := Parse(input) + + assert.Equal(t, 2, len(pairs)) + assert.Equal(t, "Key1", string(pairs[0].Key)) + assert.Equal(t, "Value1", string(pairs[0].Value)) + assert.Equal(t, "Key2", string(pairs[1].Key)) + assert.Equal(t, "Value2", string(pairs[1].Value)) +} + +func TestInvalidTrailer(t *testing.T) { + // When a string like this is included in a commit message, Git for some + // reason treats it as a trailer. + input := []byte("(cherry picked from commit ABC)") + pairs := Parse(input) + + assert.Equal(t, 0, len(pairs)) +} diff --git a/internal/gitaly/service/commit/find_commit.go b/internal/gitaly/service/commit/find_commit.go index 9e149d3c60..c1bc0fe41a 100644 --- a/internal/gitaly/service/commit/find_commit.go +++ b/internal/gitaly/service/commit/find_commit.go @@ -17,7 +17,7 @@ func (s *server) FindCommit(ctx context.Context, in *gitalypb.FindCommitRequest) repo := in.GetRepository() - commit, err := log.GetCommit(ctx, s.locator, repo, string(revision)) + commit, err := log.GetCommitWithTrailers(ctx, s.locator, repo, string(revision)) if log.IsNotFound(err) { return &gitalypb.FindCommitResponse{}, nil } diff --git a/internal/gitaly/service/commit/find_commit_test.go b/internal/gitaly/service/commit/find_commit_test.go index 166a378d2a..ce49ad7f13 100644 --- a/internal/gitaly/service/commit/find_commit_test.go +++ b/internal/gitaly/service/commit/find_commit_test.go @@ -56,11 +56,36 @@ func TestSuccessfulFindCommitRequest(t *testing.T) { revision: "branch-merged", commit: testhelper.GitLabTestCommit("498214de67004b1da3d820901307bed2a68a8ef6"), }, - { description: "With a tag name", revision: "v1.0.0", - commit: testhelper.GitLabTestCommit("6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"), + commit: &gitalypb.GitCommit{ + Id: "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9", + Subject: []byte("More submodules"), + Body: []byte("More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \n"), + Author: &gitalypb.CommitAuthor{ + Name: []byte("Dmitriy Zaporozhets"), + Email: []byte("dmitriy.zaporozhets@gmail.com"), + Date: ×tamp.Timestamp{Seconds: 1393491261}, + Timezone: []byte("+0200"), + }, + Committer: &gitalypb.CommitAuthor{ + Name: []byte("Dmitriy Zaporozhets"), + Email: []byte("dmitriy.zaporozhets@gmail.com"), + Date: ×tamp.Timestamp{Seconds: 1393491261}, + Timezone: []byte("+0200"), + }, + ParentIds: []string{"d14d6c0abdd253381df51a723d58691b2ee1ab08"}, + BodySize: 84, + SignatureType: gitalypb.SignatureType_PGP, + TreeId: "70d69cce111b0e1f54f7e5438bbbba9511a8e23c", + Trailers: []*gitalypb.CommitTrailer{ + &gitalypb.CommitTrailer{ + Key: []byte("Signed-off-by"), + Value: []byte("Dmitriy Zaporozhets "), + }, + }, + }, }, { description: "With a hash", @@ -72,6 +97,37 @@ func TestSuccessfulFindCommitRequest(t *testing.T) { revision: "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863", commit: testhelper.GitLabTestCommit("1a0b36b3cdad1d2ee32457c102a8c0b7056fa863"), }, + { + description: "More submodules", + revision: "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9", + commit: &gitalypb.GitCommit{ + Id: "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9", + Subject: []byte("More submodules"), + Body: []byte("More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \n"), + Author: &gitalypb.CommitAuthor{ + Name: []byte("Dmitriy Zaporozhets"), + Email: []byte("dmitriy.zaporozhets@gmail.com"), + Date: ×tamp.Timestamp{Seconds: 1393491261}, + Timezone: []byte("+0200"), + }, + Committer: &gitalypb.CommitAuthor{ + Name: []byte("Dmitriy Zaporozhets"), + Email: []byte("dmitriy.zaporozhets@gmail.com"), + Date: ×tamp.Timestamp{Seconds: 1393491261}, + Timezone: []byte("+0200"), + }, + ParentIds: []string{"d14d6c0abdd253381df51a723d58691b2ee1ab08"}, + BodySize: 84, + SignatureType: gitalypb.SignatureType_PGP, + TreeId: "70d69cce111b0e1f54f7e5438bbbba9511a8e23c", + Trailers: []*gitalypb.CommitTrailer{ + &gitalypb.CommitTrailer{ + Key: []byte("Signed-off-by"), + Value: []byte("Dmitriy Zaporozhets "), + }, + }, + }, + }, { description: "with non-utf8 message encoding, recognized by Git", revision: "c809470461118b7bcab850f6e9a7ca97ac42f8ea", diff --git a/internal/gitaly/service/commit/find_commits.go b/internal/gitaly/service/commit/find_commits.go index ca95a1429d..f135f0cec3 100644 --- a/internal/gitaly/service/commit/find_commits.go +++ b/internal/gitaly/service/commit/find_commits.go @@ -12,6 +12,7 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/git" "gitlab.com/gitlab-org/gitaly/internal/git/catfile" "gitlab.com/gitlab-org/gitaly/internal/git/log" + "gitlab.com/gitlab-org/gitaly/internal/git/trailerparser" "gitlab.com/gitlab-org/gitaly/internal/helper" "gitlab.com/gitlab-org/gitaly/internal/helper/chunk" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" @@ -111,11 +112,17 @@ func (g *GetCommits) Offset(offset int) error { // Commit returns the current commit func (g *GetCommits) Commit(ctx context.Context) (*gitalypb.GitCommit, error) { - revision := strings.TrimSpace(g.scanner.Text()) + revAndTrailers := strings.SplitN(strings.TrimSpace(g.scanner.Text()), "\000", 2) + revision := revAndTrailers[0] commit, err := log.GetCommitCatfile(ctx, g.batch, revision) if err != nil { return nil, fmt.Errorf("cat-file get commit %q: %v", revision, err) } + + if len(revAndTrailers) == 2 { + commit.Trailers = trailerparser.Parse([]byte(revAndTrailers[1])) + } + return commit, nil } @@ -156,7 +163,7 @@ func streamCommits(getCommits *GetCommits, stream gitalypb.CommitService_FindCom } func getLogCommandSubCmd(req *gitalypb.FindCommitsRequest) git.SubCmd { - subCmd := git.SubCmd{Name: "log", Flags: []git.Option{git.Flag{Name: "--format=format:%H"}}} + subCmd := git.SubCmd{Name: "log", Flags: []git.Option{git.Flag{Name: "--format=%H%x00%(trailers:unfold,separator=%x00)"}}} // We will perform the offset in Go because --follow doesn't play well with --skip. // See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520 diff --git a/internal/gitaly/service/commit/find_commits_test.go b/internal/gitaly/service/commit/find_commits_test.go index a0d777a2c1..eb6d2f14d3 100644 --- a/internal/gitaly/service/commit/find_commits_test.go +++ b/internal/gitaly/service/commit/find_commits_test.go @@ -73,6 +73,59 @@ func TestFindCommitsFields(t *testing.T) { id: "189a6c924013fc3fe40d6f1ec1dc20214183bc97", commit: testhelper.GitLabTestCommit("189a6c924013fc3fe40d6f1ec1dc20214183bc97"), }, + { + id: "5937ac0a7beb003549fc5fd26fc247adbce4a52e", + commit: &gitalypb.GitCommit{ + Id: "5937ac0a7beb003549fc5fd26fc247adbce4a52e", + Subject: []byte("Add submodule from gitlab.com"), + Body: []byte("Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \n"), + Author: &gitalypb.CommitAuthor{ + Name: []byte("Dmitriy Zaporozhets"), + Email: []byte("dmitriy.zaporozhets@gmail.com"), + Date: ×tamp.Timestamp{Seconds: 1393491698}, + Timezone: []byte("+0200"), + }, + Committer: &gitalypb.CommitAuthor{ + Name: []byte("Dmitriy Zaporozhets"), + Email: []byte("dmitriy.zaporozhets@gmail.com"), + Date: ×tamp.Timestamp{Seconds: 1393491698}, + Timezone: []byte("+0200"), + }, + ParentIds: []string{"570e7b2abdd848b95f2f578043fc23bd6f6fd24d"}, + BodySize: 98, + SignatureType: gitalypb.SignatureType_PGP, + TreeId: "a6973545d42361b28bfba5ced3b75dba5848b955", + Trailers: []*gitalypb.CommitTrailer{ + &gitalypb.CommitTrailer{ + Key: []byte("Signed-off-by"), + Value: []byte("Dmitriy Zaporozhets "), + }, + }, + }, + }, + { + id: "c1c67abbaf91f624347bb3ae96eabe3a1b742478", + commit: &gitalypb.GitCommit{ + Id: "c1c67abbaf91f624347bb3ae96eabe3a1b742478", + Subject: []byte("Add file with a _flattable_ path"), + Body: []byte("Add file with a _flattable_ path\n\n\n(cherry picked from commit ce369011c189f62c815f5971d096b26759bab0d1)"), + Author: &gitalypb.CommitAuthor{ + Name: []byte("Alejandro Rodríguez"), + Email: []byte("alejorro70@gmail.com"), + Date: ×tamp.Timestamp{Seconds: 1504382739}, + Timezone: []byte("+0000"), + }, + Committer: &gitalypb.CommitAuthor{ + Name: []byte("Drew Blessing"), + Email: []byte("drew@blessing.io"), + Date: ×tamp.Timestamp{Seconds: 1540823671}, + Timezone: []byte("+0000"), + }, + ParentIds: []string{"7975be0116940bf2ad4321f79d02a55c5f7779aa"}, + BodySize: 103, + TreeId: "07f8147e8e73aab6c935c296e8cdc5194dee729b", + }, + }, } for _, tc := range testCases { diff --git a/proto/go/gitalypb/shared.pb.go b/proto/go/gitalypb/shared.pb.go index 724ffc2464..50e47b81da 100644 --- a/proto/go/gitalypb/shared.pb.go +++ b/proto/go/gitalypb/shared.pb.go @@ -174,6 +174,57 @@ func (m *Repository) GetGlProjectPath() string { return "" } +// A single Git trailer (https://git-scm.com/docs/git-interpret-trailers) +// key-value pair. +type CommitTrailer struct { + // The key of the trailer, such as `Signed-off-by`. + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // The value of the trailer, such as `Alice `. + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CommitTrailer) Reset() { *m = CommitTrailer{} } +func (m *CommitTrailer) String() string { return proto.CompactTextString(m) } +func (*CommitTrailer) ProtoMessage() {} +func (*CommitTrailer) Descriptor() ([]byte, []int) { + return fileDescriptor_d8a4e87e678c5ced, []int{1} +} + +func (m *CommitTrailer) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CommitTrailer.Unmarshal(m, b) +} +func (m *CommitTrailer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CommitTrailer.Marshal(b, m, deterministic) +} +func (m *CommitTrailer) XXX_Merge(src proto.Message) { + xxx_messageInfo_CommitTrailer.Merge(m, src) +} +func (m *CommitTrailer) XXX_Size() int { + return xxx_messageInfo_CommitTrailer.Size(m) +} +func (m *CommitTrailer) XXX_DiscardUnknown() { + xxx_messageInfo_CommitTrailer.DiscardUnknown(m) +} + +var xxx_messageInfo_CommitTrailer proto.InternalMessageInfo + +func (m *CommitTrailer) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func (m *CommitTrailer) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + // Corresponds to Gitlab::Git::Commit type GitCommit struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -190,17 +241,22 @@ type GitCommit struct { // The tree ID will always be filled, even if the tree is empty. In that case // the value will be `4b825dc642cb6eb9a060e54bf8d69288fbee4904`. // That value is equivalent to `git hash-object -t tree /dev/null` - TreeId string `protobuf:"bytes,9,opt,name=tree_id,json=treeId,proto3" json:"tree_id,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + TreeId string `protobuf:"bytes,9,opt,name=tree_id,json=treeId,proto3" json:"tree_id,omitempty"` + // The list of Git trailers (https://git-scm.com/docs/git-interpret-trailers) + // found in this commit's message. The number of trailers and their key/value + // sizes are limited. If a trailer exceeds these size limits, it and any + // trailers that follow it are not included. + Trailers []*CommitTrailer `protobuf:"bytes,10,rep,name=trailers,proto3" json:"trailers,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *GitCommit) Reset() { *m = GitCommit{} } func (m *GitCommit) String() string { return proto.CompactTextString(m) } func (*GitCommit) ProtoMessage() {} func (*GitCommit) Descriptor() ([]byte, []int) { - return fileDescriptor_d8a4e87e678c5ced, []int{1} + return fileDescriptor_d8a4e87e678c5ced, []int{2} } func (m *GitCommit) XXX_Unmarshal(b []byte) error { @@ -284,6 +340,13 @@ func (m *GitCommit) GetTreeId() string { return "" } +func (m *GitCommit) GetTrailers() []*CommitTrailer { + if m != nil { + return m.Trailers + } + return nil +} + type CommitAuthor struct { Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Email []byte `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` @@ -298,7 +361,7 @@ func (m *CommitAuthor) Reset() { *m = CommitAuthor{} } func (m *CommitAuthor) String() string { return proto.CompactTextString(m) } func (*CommitAuthor) ProtoMessage() {} func (*CommitAuthor) Descriptor() ([]byte, []int) { - return fileDescriptor_d8a4e87e678c5ced, []int{2} + return fileDescriptor_d8a4e87e678c5ced, []int{3} } func (m *CommitAuthor) XXX_Unmarshal(b []byte) error { @@ -358,7 +421,7 @@ func (m *ExitStatus) Reset() { *m = ExitStatus{} } func (m *ExitStatus) String() string { return proto.CompactTextString(m) } func (*ExitStatus) ProtoMessage() {} func (*ExitStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_d8a4e87e678c5ced, []int{3} + return fileDescriptor_d8a4e87e678c5ced, []int{4} } func (m *ExitStatus) XXX_Unmarshal(b []byte) error { @@ -399,7 +462,7 @@ func (m *Branch) Reset() { *m = Branch{} } func (m *Branch) String() string { return proto.CompactTextString(m) } func (*Branch) ProtoMessage() {} func (*Branch) Descriptor() ([]byte, []int) { - return fileDescriptor_d8a4e87e678c5ced, []int{4} + return fileDescriptor_d8a4e87e678c5ced, []int{5} } func (m *Branch) XXX_Unmarshal(b []byte) error { @@ -454,7 +517,7 @@ func (m *Tag) Reset() { *m = Tag{} } func (m *Tag) String() string { return proto.CompactTextString(m) } func (*Tag) ProtoMessage() {} func (*Tag) Descriptor() ([]byte, []int) { - return fileDescriptor_d8a4e87e678c5ced, []int{5} + return fileDescriptor_d8a4e87e678c5ced, []int{6} } func (m *Tag) XXX_Unmarshal(b []byte) error { @@ -538,7 +601,7 @@ func (m *User) Reset() { *m = User{} } func (m *User) String() string { return proto.CompactTextString(m) } func (*User) ProtoMessage() {} func (*User) Descriptor() ([]byte, []int) { - return fileDescriptor_d8a4e87e678c5ced, []int{6} + return fileDescriptor_d8a4e87e678c5ced, []int{7} } func (m *User) XXX_Unmarshal(b []byte) error { @@ -598,7 +661,7 @@ func (m *ObjectPool) Reset() { *m = ObjectPool{} } func (m *ObjectPool) String() string { return proto.CompactTextString(m) } func (*ObjectPool) ProtoMessage() {} func (*ObjectPool) Descriptor() ([]byte, []int) { - return fileDescriptor_d8a4e87e678c5ced, []int{7} + return fileDescriptor_d8a4e87e678c5ced, []int{8} } func (m *ObjectPool) XXX_Unmarshal(b []byte) error { @@ -649,7 +712,7 @@ func (m *PaginationParameter) Reset() { *m = PaginationParameter{} } func (m *PaginationParameter) String() string { return proto.CompactTextString(m) } func (*PaginationParameter) ProtoMessage() {} func (*PaginationParameter) Descriptor() ([]byte, []int) { - return fileDescriptor_d8a4e87e678c5ced, []int{8} + return fileDescriptor_d8a4e87e678c5ced, []int{9} } func (m *PaginationParameter) XXX_Unmarshal(b []byte) error { @@ -697,7 +760,7 @@ func (m *GlobalOptions) Reset() { *m = GlobalOptions{} } func (m *GlobalOptions) String() string { return proto.CompactTextString(m) } func (*GlobalOptions) ProtoMessage() {} func (*GlobalOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_d8a4e87e678c5ced, []int{9} + return fileDescriptor_d8a4e87e678c5ced, []int{10} } func (m *GlobalOptions) XXX_Unmarshal(b []byte) error { @@ -729,6 +792,7 @@ func init() { proto.RegisterEnum("gitaly.ObjectType", ObjectType_name, ObjectType_value) proto.RegisterEnum("gitaly.SignatureType", SignatureType_name, SignatureType_value) proto.RegisterType((*Repository)(nil), "gitaly.Repository") + proto.RegisterType((*CommitTrailer)(nil), "gitaly.CommitTrailer") proto.RegisterType((*GitCommit)(nil), "gitaly.GitCommit") proto.RegisterType((*CommitAuthor)(nil), "gitaly.CommitAuthor") proto.RegisterType((*ExitStatus)(nil), "gitaly.ExitStatus") @@ -743,61 +807,64 @@ func init() { func init() { proto.RegisterFile("shared.proto", fileDescriptor_d8a4e87e678c5ced) } var fileDescriptor_d8a4e87e678c5ced = []byte{ - // 886 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0x41, 0x6f, 0x23, 0x35, - 0x14, 0xde, 0x4c, 0x26, 0x93, 0xe4, 0x25, 0x29, 0xb3, 0xde, 0x22, 0x46, 0x45, 0xab, 0x2d, 0x83, - 0x84, 0xaa, 0xa5, 0xa4, 0x55, 0x10, 0x08, 0xa4, 0xbd, 0x34, 0x4b, 0x89, 0x5a, 0xd8, 0x24, 0x9a, - 0xa6, 0x02, 0x71, 0x19, 0x39, 0x19, 0xaf, 0x63, 0xf0, 0x8c, 0x47, 0xb6, 0xb3, 0x22, 0x3d, 0x73, - 0xe2, 0xc4, 0x2f, 0xe1, 0x27, 0x70, 0xe4, 0x77, 0x21, 0xdb, 0x33, 0xd9, 0x2c, 0x94, 0xd5, 0xde, - 0xfc, 0x3e, 0x7f, 0xf3, 0xfc, 0xde, 0xe7, 0xcf, 0x6f, 0xa0, 0xaf, 0xd6, 0x58, 0x92, 0x6c, 0x58, - 0x4a, 0xa1, 0x05, 0x0a, 0x28, 0xd3, 0x98, 0x6f, 0x8f, 0x9e, 0x50, 0x21, 0x28, 0x27, 0x67, 0x16, - 0x5d, 0x6e, 0x5e, 0x9e, 0x69, 0x96, 0x13, 0xa5, 0x71, 0x5e, 0x3a, 0xe2, 0x11, 0x70, 0x56, 0x68, - 0xb7, 0x8e, 0xff, 0xf4, 0x00, 0x12, 0x52, 0x0a, 0xc5, 0xb4, 0x90, 0x5b, 0xf4, 0x11, 0xf4, 0x95, - 0x16, 0x12, 0x53, 0x92, 0x16, 0x38, 0x27, 0x91, 0x77, 0xdc, 0x38, 0xe9, 0x26, 0xbd, 0x0a, 0x9b, - 0xe2, 0x9c, 0xa0, 0x8f, 0x61, 0x20, 0x09, 0xc7, 0x9a, 0xbd, 0x22, 0x69, 0x89, 0xf5, 0x3a, 0x6a, - 0x5a, 0x4e, 0xbf, 0x06, 0xe7, 0x58, 0xaf, 0xd1, 0x39, 0x1c, 0x52, 0xa6, 0x53, 0xb1, 0xfc, 0x99, - 0xac, 0x74, 0x9a, 0x31, 0x49, 0x56, 0x26, 0x7f, 0xe4, 0x5b, 0x2e, 0xa2, 0x4c, 0xcf, 0xec, 0xd6, - 0x37, 0xf5, 0x0e, 0x9a, 0xc0, 0xb1, 0xf9, 0x02, 0x73, 0x4d, 0x64, 0x81, 0x35, 0xf9, 0xf7, 0xb7, - 0x8c, 0xa8, 0xa8, 0x75, 0xdc, 0x3c, 0xe9, 0x26, 0x8f, 0x29, 0xd3, 0x17, 0x35, 0xed, 0xcd, 0x34, - 0x8c, 0x28, 0x53, 0x1f, 0xe5, 0xa9, 0xdc, 0xf5, 0x14, 0x05, 0xae, 0x3e, 0xca, 0xf7, 0xfa, 0xfc, - 0x04, 0xde, 0xa3, 0x3c, 0x2d, 0xa5, 0xb0, 0x67, 0xd8, 0x36, 0x3a, 0x96, 0x36, 0xa0, 0x7c, 0xee, - 0x50, 0xd3, 0xc7, 0xb5, 0xdf, 0x69, 0x84, 0xde, 0xb5, 0xdf, 0x69, 0x87, 0x9d, 0xc4, 0x37, 0xb4, - 0xf8, 0x2f, 0x0f, 0xba, 0x13, 0xa6, 0x9f, 0x8b, 0x3c, 0x67, 0x1a, 0x1d, 0x80, 0xc7, 0xb2, 0xa8, - 0x61, 0x3f, 0xf5, 0x58, 0x86, 0x22, 0x68, 0xab, 0x8d, 0x2d, 0xc9, 0x4a, 0xd7, 0x4f, 0xea, 0x10, - 0x21, 0xf0, 0x97, 0x22, 0xdb, 0x5a, 0xb5, 0xfa, 0x89, 0x5d, 0xa3, 0x53, 0x08, 0xf0, 0x46, 0xaf, - 0x85, 0xb4, 0xba, 0xf4, 0x46, 0x87, 0x43, 0x77, 0x85, 0x43, 0x97, 0xfd, 0xc2, 0xee, 0x25, 0x15, - 0x07, 0x8d, 0xa0, 0xbb, 0xb2, 0xb8, 0x26, 0x32, 0x6a, 0xbd, 0xe5, 0x83, 0xd7, 0x34, 0xf4, 0x18, - 0xa0, 0xc4, 0x92, 0x14, 0x3a, 0x65, 0x99, 0x8a, 0x02, 0xab, 0x5f, 0xd7, 0x21, 0x57, 0x99, 0x42, - 0x1f, 0x42, 0xd7, 0x14, 0x92, 0x2a, 0x76, 0x47, 0xa2, 0xf6, 0x71, 0xe3, 0xa4, 0x99, 0x74, 0x0c, - 0x70, 0xc3, 0xee, 0x08, 0x7a, 0x06, 0x07, 0x8a, 0xd1, 0x02, 0xeb, 0x8d, 0x24, 0xa9, 0xde, 0x96, - 0xc4, 0x4a, 0x74, 0x30, 0x7a, 0xbf, 0x3e, 0xf4, 0xa6, 0xde, 0x5d, 0x6c, 0x4b, 0x92, 0x0c, 0xd4, - 0x7e, 0x88, 0x3e, 0x80, 0xb6, 0x96, 0x84, 0xa4, 0x2c, 0x8b, 0xba, 0x56, 0x9e, 0xc0, 0x84, 0x57, - 0x59, 0xfc, 0x5b, 0x03, 0xfa, 0xfb, 0xe5, 0x1a, 0x65, 0xac, 0xd7, 0x1a, 0x4e, 0x19, 0xb3, 0x46, - 0x87, 0xd0, 0x22, 0x39, 0x66, 0xbc, 0x52, 0xd1, 0x05, 0x68, 0x08, 0x7e, 0x86, 0x35, 0xb1, 0x1a, - 0xf6, 0x46, 0x47, 0x43, 0x67, 0xf4, 0x61, 0x6d, 0xf4, 0xe1, 0xa2, 0x36, 0x7a, 0x62, 0x79, 0xe8, - 0x08, 0x3a, 0xc6, 0xfb, 0x77, 0xa2, 0x20, 0x56, 0xe1, 0x7e, 0xb2, 0x8b, 0xe3, 0x18, 0xe0, 0xf2, - 0x57, 0xa6, 0x6f, 0x34, 0xd6, 0x1b, 0x65, 0xce, 0x7b, 0x85, 0xf9, 0xc6, 0x15, 0xd1, 0x4a, 0x5c, - 0x10, 0x2f, 0x20, 0x18, 0x4b, 0x5c, 0xac, 0xd6, 0xf7, 0xd6, 0xf8, 0x25, 0x0c, 0x34, 0x96, 0x94, - 0xe8, 0xd4, 0xe9, 0x6d, 0x6b, 0xed, 0x8d, 0x1e, 0xd6, 0xf2, 0xec, 0x5c, 0x92, 0xf4, 0x1d, 0xcf, - 0x45, 0xf1, 0xef, 0x1e, 0x34, 0x17, 0x98, 0xde, 0x9b, 0xd3, 0xf9, 0xc9, 0xdb, 0xf9, 0xe9, 0x3f, - 0x67, 0x34, 0xdf, 0xe9, 0x0c, 0xe3, 0xc3, 0x9c, 0x28, 0x85, 0x69, 0xdd, 0x78, 0x1d, 0x9a, 0x17, - 0x5e, 0x2d, 0xdd, 0xad, 0xb7, 0xec, 0xad, 0xf7, 0x2a, 0xcc, 0x5e, 0xfc, 0x29, 0x04, 0x1a, 0x53, - 0x4a, 0xa4, 0x7d, 0x3a, 0xff, 0x6b, 0x4b, 0xc7, 0xb9, 0xc7, 0x26, 0xed, 0x77, 0xb7, 0x49, 0xfc, - 0x12, 0xfc, 0x5b, 0x45, 0x24, 0x7a, 0x04, 0x2d, 0xca, 0xd3, 0xdd, 0x5b, 0xf2, 0x29, 0xbf, 0xca, - 0x76, 0x0a, 0x79, 0xf7, 0x39, 0xa3, 0xb9, 0xef, 0x8c, 0x27, 0xd0, 0xa3, 0x3c, 0xdd, 0x28, 0x33, - 0x14, 0x72, 0x52, 0x8d, 0x19, 0xa0, 0xfc, 0xb6, 0x42, 0xe2, 0x6f, 0x01, 0xdc, 0xa8, 0x98, 0x0b, - 0xc1, 0xd1, 0x57, 0x00, 0x7b, 0x03, 0xa2, 0x61, 0xbb, 0x44, 0x75, 0xbd, 0xaf, 0xc7, 0xc4, 0xd8, - 0xff, 0xe3, 0xef, 0xd3, 0x46, 0xb2, 0xc7, 0x8d, 0xaf, 0xe1, 0xd1, 0x1c, 0x53, 0x56, 0x60, 0xcd, - 0x44, 0x31, 0xc7, 0x12, 0xe7, 0x64, 0xf7, 0xce, 0x28, 0x49, 0xb5, 0xf8, 0x85, 0x14, 0x55, 0x0f, - 0x5d, 0x83, 0x2c, 0x0c, 0x60, 0x8a, 0xe6, 0xac, 0xb6, 0x48, 0x2b, 0x71, 0x41, 0xfc, 0x0c, 0x06, - 0x13, 0x2e, 0x96, 0x98, 0xcf, 0x4a, 0x93, 0x4d, 0xa1, 0x4f, 0xe1, 0x21, 0x67, 0x9a, 0x48, 0xcc, - 0xed, 0x48, 0x52, 0x25, 0x59, 0x29, 0x9b, 0xac, 0x93, 0x84, 0xd5, 0xc6, 0xbc, 0xc6, 0x9f, 0x8e, - 0xeb, 0x8e, 0xec, 0x73, 0xeb, 0x41, 0xfb, 0x76, 0xfa, 0xdd, 0x74, 0xf6, 0xc3, 0x34, 0x7c, 0x80, - 0x00, 0x82, 0xe7, 0xb3, 0x17, 0x2f, 0xae, 0x16, 0x61, 0x03, 0x75, 0xc0, 0x1f, 0x7f, 0x3f, 0x1b, - 0x87, 0x9e, 0x59, 0x2d, 0x92, 0xcb, 0xcb, 0xb0, 0x89, 0xda, 0xd0, 0x5c, 0x5c, 0x4c, 0x42, 0xff, - 0xe9, 0x29, 0x0c, 0xde, 0xb8, 0x1d, 0xc3, 0x99, 0xce, 0xa6, 0x97, 0xe1, 0x03, 0xc3, 0x99, 0x4f, - 0xe6, 0x2e, 0xc1, 0x8f, 0x5f, 0x9c, 0x7f, 0x1d, 0x7a, 0xe3, 0xf3, 0x9f, 0x8c, 0x44, 0x1c, 0x2f, - 0x87, 0x2b, 0x91, 0x9f, 0xb9, 0xe5, 0x67, 0x42, 0xd2, 0x33, 0x27, 0x9c, 0xfb, 0xe1, 0x9c, 0x51, - 0x51, 0xc5, 0xe5, 0x72, 0x19, 0x58, 0xe8, 0xf3, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x2f, 0x36, - 0xf5, 0xd4, 0xa9, 0x06, 0x00, 0x00, + // 934 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0x5f, 0x6f, 0xe3, 0x44, + 0x10, 0xbf, 0xd8, 0xce, 0xbf, 0x49, 0x72, 0xf8, 0xf6, 0x8a, 0xb0, 0x8a, 0x4e, 0x17, 0x8c, 0x84, + 0xaa, 0xa3, 0xa4, 0xa5, 0x88, 0x7f, 0xd2, 0xbd, 0x34, 0x47, 0xa9, 0x5a, 0xb8, 0x24, 0x72, 0x53, + 0x81, 0x78, 0xb1, 0x36, 0xf1, 0xde, 0x66, 0xb9, 0xb5, 0xd7, 0xda, 0xdd, 0x9c, 0x48, 0x9f, 0x79, + 0xe2, 0x89, 0x4f, 0xc2, 0xc7, 0xe0, 0xfb, 0xf0, 0x0d, 0xd0, 0xee, 0xda, 0x69, 0x0a, 0x05, 0xdd, + 0xdb, 0xce, 0xec, 0x6f, 0xc7, 0x33, 0xbf, 0xf9, 0xcd, 0x18, 0xfa, 0x6a, 0x85, 0x25, 0xc9, 0x46, + 0xa5, 0x14, 0x5a, 0xa0, 0x16, 0x65, 0x1a, 0xf3, 0xcd, 0xfe, 0x53, 0x2a, 0x04, 0xe5, 0xe4, 0xc8, + 0x7a, 0x17, 0xeb, 0x57, 0x47, 0x9a, 0xe5, 0x44, 0x69, 0x9c, 0x97, 0x0e, 0xb8, 0x0f, 0x9c, 0x15, + 0xda, 0x9d, 0xe3, 0x3f, 0x3c, 0x80, 0x84, 0x94, 0x42, 0x31, 0x2d, 0xe4, 0x06, 0x7d, 0x00, 0x7d, + 0xa5, 0x85, 0xc4, 0x94, 0xa4, 0x05, 0xce, 0x49, 0xe4, 0x0d, 0x1b, 0x07, 0xdd, 0xa4, 0x57, 0xf9, + 0x26, 0x38, 0x27, 0xe8, 0x43, 0x18, 0x48, 0xc2, 0xb1, 0x66, 0x6f, 0x48, 0x5a, 0x62, 0xbd, 0x8a, + 0x7c, 0x8b, 0xe9, 0xd7, 0xce, 0x19, 0xd6, 0x2b, 0x74, 0x0c, 0x7b, 0x94, 0xe9, 0x54, 0x2c, 0x7e, + 0x26, 0x4b, 0x9d, 0x66, 0x4c, 0x92, 0xa5, 0x89, 0x1f, 0x05, 0x16, 0x8b, 0x28, 0xd3, 0x53, 0x7b, + 0xf5, 0x4d, 0x7d, 0x83, 0xce, 0x61, 0x68, 0x5e, 0x60, 0xae, 0x89, 0x2c, 0xb0, 0x26, 0xff, 0x7c, + 0xcb, 0x88, 0x8a, 0x9a, 0x43, 0xff, 0xa0, 0x9b, 0x3c, 0xa1, 0x4c, 0x9f, 0xd6, 0xb0, 0xbb, 0x61, + 0x18, 0x51, 0x26, 0x3f, 0xca, 0x53, 0xb9, 0xad, 0x29, 0x6a, 0xb9, 0xfc, 0x28, 0xdf, 0xa9, 0xf3, + 0x23, 0x78, 0x87, 0xf2, 0xb4, 0x94, 0xc2, 0x7e, 0xc3, 0x96, 0xd1, 0xb1, 0xb0, 0x01, 0xe5, 0x33, + 0xe7, 0x35, 0x75, 0x5c, 0x06, 0x9d, 0x46, 0xe8, 0x5d, 0x06, 0x9d, 0x76, 0xd8, 0x49, 0x02, 0x03, + 0x8b, 0xbf, 0x84, 0xc1, 0x0b, 0x91, 0xe7, 0x4c, 0xcf, 0x25, 0x66, 0x9c, 0x48, 0x14, 0x82, 0xff, + 0x9a, 0x6c, 0xa2, 0xc6, 0xb0, 0x71, 0xd0, 0x4f, 0xcc, 0x11, 0xed, 0x41, 0xf3, 0x0d, 0xe6, 0x6b, + 0xc7, 0x5e, 0x3f, 0x71, 0x46, 0xfc, 0x97, 0x07, 0xdd, 0x73, 0xa6, 0xdd, 0x63, 0xf4, 0x10, 0x3c, + 0x96, 0xd9, 0x47, 0xdd, 0xc4, 0x63, 0x19, 0x8a, 0xa0, 0xad, 0xd6, 0xb6, 0x96, 0xea, 0x55, 0x6d, + 0x22, 0x04, 0xc1, 0x42, 0x64, 0x1b, 0x4b, 0x73, 0x3f, 0xb1, 0x67, 0x74, 0x08, 0x2d, 0xbc, 0xd6, + 0x2b, 0x21, 0x2d, 0xa1, 0xbd, 0x93, 0xbd, 0x91, 0xeb, 0xfd, 0xc8, 0x45, 0x3f, 0xb5, 0x77, 0x49, + 0x85, 0x41, 0x27, 0xd0, 0x5d, 0x5a, 0xbf, 0x26, 0x32, 0x6a, 0xfe, 0xcf, 0x83, 0x5b, 0x18, 0x7a, + 0x02, 0x50, 0x62, 0x49, 0x0a, 0x9d, 0xb2, 0x4c, 0x45, 0x2d, 0x4b, 0x7c, 0xd7, 0x79, 0x2e, 0x32, + 0x85, 0xde, 0x87, 0xae, 0x49, 0x24, 0x55, 0xec, 0x86, 0x44, 0xed, 0x61, 0xe3, 0xc0, 0x4f, 0x3a, + 0xc6, 0x71, 0xc5, 0x6e, 0x08, 0x7a, 0x0e, 0x0f, 0x15, 0xa3, 0x05, 0xd6, 0x6b, 0x49, 0x52, 0xbd, + 0x29, 0x89, 0xe5, 0xf6, 0xe1, 0xc9, 0xbb, 0xf5, 0x47, 0xaf, 0xea, 0xdb, 0xf9, 0xa6, 0x24, 0xc9, + 0x40, 0xed, 0x9a, 0xe8, 0x3d, 0x68, 0x6b, 0x49, 0x48, 0xca, 0xb2, 0xa8, 0x6b, 0xe9, 0x69, 0x19, + 0xf3, 0x22, 0x43, 0x9f, 0x42, 0x47, 0x3b, 0xce, 0x55, 0x04, 0x43, 0xff, 0xa0, 0x77, 0x1b, 0xf0, + 0x4e, 0x47, 0x92, 0x2d, 0x2c, 0xfe, 0xb5, 0x01, 0xfd, 0xdd, 0x0a, 0x0d, 0x99, 0x56, 0xd7, 0xae, + 0x5b, 0xf6, 0x6c, 0xda, 0x45, 0x72, 0xcc, 0x78, 0xdd, 0x2e, 0x6b, 0xa0, 0x11, 0x04, 0x19, 0xd6, + 0xc4, 0xd2, 0xde, 0x3b, 0xd9, 0x1f, 0xb9, 0xa1, 0x1a, 0xd5, 0x43, 0x35, 0x9a, 0xd7, 0x43, 0x95, + 0x58, 0x1c, 0xda, 0x87, 0x8e, 0x99, 0xb3, 0x1b, 0x51, 0x10, 0xdb, 0x94, 0x7e, 0xb2, 0xb5, 0xe3, + 0x18, 0xe0, 0xec, 0x17, 0xa6, 0xaf, 0x34, 0xd6, 0x6b, 0x75, 0x2b, 0x0f, 0x93, 0x44, 0xb3, 0x96, + 0xc7, 0x1c, 0x5a, 0x63, 0x89, 0x8b, 0xe5, 0xea, 0xde, 0x1c, 0xbf, 0x80, 0x81, 0xc6, 0x92, 0x12, + 0x9d, 0xba, 0x16, 0xd9, 0x5c, 0x7b, 0x27, 0x8f, 0x6a, 0x02, 0xb6, 0xc2, 0x4a, 0xfa, 0x0e, 0xe7, + 0xac, 0xf8, 0x37, 0x0f, 0xfc, 0x39, 0xa6, 0xf7, 0xc6, 0x74, 0x12, 0xf4, 0xb6, 0x12, 0xfc, 0xd7, + 0x37, 0xfc, 0xb7, 0xfa, 0x86, 0x91, 0x6e, 0x4e, 0x94, 0xc2, 0xb4, 0x2e, 0xbc, 0x36, 0xcd, 0x36, + 0xa9, 0x8e, 0x4e, 0x28, 0x4d, 0x2b, 0x94, 0x5e, 0xe5, 0xb3, 0x5a, 0x39, 0x84, 0x96, 0xc6, 0x94, + 0x12, 0x69, 0xc7, 0xf4, 0x3f, 0x95, 0xec, 0x30, 0xf7, 0x28, 0xab, 0xfd, 0xf6, 0xca, 0x8a, 0x5f, + 0x41, 0x70, 0xad, 0x88, 0x44, 0x8f, 0xa1, 0x49, 0x79, 0xba, 0x1d, 0xbf, 0x80, 0xf2, 0x8b, 0x6c, + 0xcb, 0x90, 0x77, 0x9f, 0x32, 0xfc, 0x5d, 0x65, 0x3c, 0x85, 0x1e, 0xe5, 0xe9, 0x5a, 0x99, 0x05, + 0x94, 0x93, 0x6a, 0xa5, 0x01, 0xe5, 0xd7, 0x95, 0x27, 0xfe, 0x16, 0xc0, 0xad, 0xa5, 0x99, 0x10, + 0x1c, 0x7d, 0x05, 0xb0, 0xb3, 0x8c, 0x1a, 0xb6, 0x4a, 0x54, 0xe7, 0x7b, 0xbb, 0x92, 0xc6, 0xc1, + 0xef, 0x7f, 0x1e, 0x36, 0x92, 0x1d, 0x6c, 0x7c, 0x09, 0x8f, 0x67, 0x98, 0xb2, 0x02, 0x6b, 0x26, + 0x8a, 0x19, 0x96, 0x38, 0x27, 0xdb, 0xd1, 0xa4, 0x24, 0xd5, 0xe2, 0x35, 0x29, 0xaa, 0x1a, 0xba, + 0xc6, 0x33, 0x37, 0x0e, 0x93, 0x34, 0x67, 0xb5, 0x44, 0x9a, 0x89, 0x33, 0xe2, 0xe7, 0x30, 0x38, + 0xe7, 0x62, 0x81, 0xf9, 0xb4, 0x34, 0xd1, 0x14, 0xfa, 0x18, 0x1e, 0x71, 0xa6, 0x89, 0xc4, 0xdc, + 0xae, 0x3f, 0x55, 0x92, 0xa5, 0xb2, 0xc1, 0x3a, 0x49, 0x58, 0x5d, 0xcc, 0x6a, 0xff, 0xb3, 0x71, + 0x5d, 0x91, 0x9d, 0xd0, 0x1e, 0xb4, 0xaf, 0x27, 0xdf, 0x4d, 0xa6, 0x3f, 0x4c, 0xc2, 0x07, 0x08, + 0xa0, 0xf5, 0x62, 0xfa, 0xf2, 0xe5, 0xc5, 0x3c, 0x6c, 0xa0, 0x0e, 0x04, 0xe3, 0xef, 0xa7, 0xe3, + 0xd0, 0x33, 0xa7, 0x79, 0x72, 0x76, 0x16, 0xfa, 0xa8, 0x0d, 0xfe, 0xfc, 0xf4, 0x3c, 0x0c, 0x9e, + 0x1d, 0xc2, 0xe0, 0x4e, 0x77, 0x0c, 0x66, 0x32, 0x9d, 0x9c, 0x85, 0x0f, 0x0c, 0x66, 0x76, 0x3e, + 0x73, 0x01, 0x7e, 0xfc, 0xfc, 0xf8, 0xeb, 0xd0, 0x1b, 0x1f, 0xff, 0x64, 0x28, 0xe2, 0x78, 0x31, + 0x5a, 0x8a, 0xfc, 0xc8, 0x1d, 0x3f, 0x11, 0x92, 0x1e, 0x39, 0xe2, 0xdc, 0xcf, 0xed, 0x88, 0x8a, + 0xca, 0x2e, 0x17, 0x8b, 0x96, 0x75, 0x7d, 0xf6, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x07, + 0x2e, 0x7a, 0x15, 0x07, 0x00, 0x00, } diff --git a/proto/shared.proto b/proto/shared.proto index 19ec946302..1d85f33098 100644 --- a/proto/shared.proto +++ b/proto/shared.proto @@ -48,6 +48,15 @@ message Repository { string gl_project_path = 8; } +// A single Git trailer (https://git-scm.com/docs/git-interpret-trailers) +// key-value pair. +message CommitTrailer { + // The key of the trailer, such as `Signed-off-by`. + bytes key = 1; + // The value of the trailer, such as `Alice `. + bytes value = 2; +} + // Corresponds to Gitlab::Git::Commit message GitCommit { string id = 1; @@ -66,6 +75,11 @@ message GitCommit { // the value will be `4b825dc642cb6eb9a060e54bf8d69288fbee4904`. // That value is equivalent to `git hash-object -t tree /dev/null` string tree_id = 9; + // The list of Git trailers (https://git-scm.com/docs/git-interpret-trailers) + // found in this commit's message. The number of trailers and their key/value + // sizes are limited. If a trailer exceeds these size limits, it and any + // trailers that follow it are not included. + repeated CommitTrailer trailers = 10; } message CommitAuthor { diff --git a/ruby/lib/gitaly_server/utils.rb b/ruby/lib/gitaly_server/utils.rb index 36cfbd859c..1d0bd89f07 100644 --- a/ruby/lib/gitaly_server/utils.rb +++ b/ruby/lib/gitaly_server/utils.rb @@ -13,7 +13,8 @@ module GitalyServer author: gitaly_commit_author_from_rugged(rugged_commit.author), committer: gitaly_commit_author_from_rugged(rugged_commit.committer), body_size: rugged_commit.message.bytesize, - tree_id: rugged_commit.tree.oid + tree_id: rugged_commit.tree.oid, + trailers: gitaly_trailers_from_rugged(rugged_commit) ) truncate_gitaly_commit_body!(gitaly_commit) if gitaly_commit.body.bytesize > Gitlab.config.git.max_commit_or_tag_message_size @@ -54,6 +55,12 @@ module GitalyServer tag end + def gitaly_trailers_from_rugged(rugged_commit) + rugged_commit.trailers.map do |(key, value)| + Gitaly::CommitTrailer.new(key: key, value: value) + end + end + def truncate_gitaly_commit_body!(gitaly_commit) gitaly_commit.body = gitaly_commit.body[0, Gitlab.config.git.max_commit_or_tag_message_size] end diff --git a/ruby/lib/gitlab/git/commit.rb b/ruby/lib/gitlab/git/commit.rb index 1e312ba0fd..542b269c1c 100644 --- a/ruby/lib/gitlab/git/commit.rb +++ b/ruby/lib/gitlab/git/commit.rb @@ -7,10 +7,10 @@ module Gitlab MAX_COMMIT_MESSAGE_DISPLAY_SIZE = 10.megabytes MIN_SHA_LENGTH = 7 - SERIALIZE_KEYS = [ - :id, :message, :parent_ids, - :authored_date, :author_name, :author_email, - :committed_date, :committer_name, :committer_email + SERIALIZE_KEYS = %i[ + id message parent_ids + authored_date author_name author_email + committed_date committer_name committer_email trailers ].freeze attr_accessor *SERIALIZE_KEYS # rubocop:disable Lint/AmbiguousOperator @@ -163,7 +163,8 @@ module Gitlab body: raw_commit.message.b, parent_ids: raw_commit.parent_ids, author: gitaly_commit_author_from_rugged(raw_commit.author), - committer: gitaly_commit_author_from_rugged(raw_commit.committer) + committer: gitaly_commit_author_from_rugged(raw_commit.committer), + trailers: gitaly_trailers_from_rugged(raw_commit) ) end @@ -202,6 +203,7 @@ module Gitlab @committer_name = committer[:name] @committer_email = committer[:email] @parent_ids = commit.parents.map(&:oid) + @trailers = Hash[commit.trailers] end def init_from_gitaly(commit) @@ -218,6 +220,7 @@ module Gitlab @committer_name = commit.committer.name.dup @committer_email = commit.committer.email.dup @parent_ids = Array(commit.parent_ids) + @trailers = Hash[commit.trailers.map { |t| [t.key, t.value] }] end def serialize_keys @@ -232,6 +235,12 @@ module Gitlab ) end + def gitaly_trailers_from_rugged(rugged_commit) + rugged_commit.trailers.map do |(key, value)| + Gitaly::CommitTrailer.new(key: key, value: value) + end + end + def message_from_gitaly_body return @raw_commit.subject.dup if @raw_commit.body_size.zero? diff --git a/ruby/proto/gitaly/shared_pb.rb b/ruby/proto/gitaly/shared_pb.rb index c816f28626..d20479f96b 100644 --- a/ruby/proto/gitaly/shared_pb.rb +++ b/ruby/proto/gitaly/shared_pb.rb @@ -15,6 +15,10 @@ Google::Protobuf::DescriptorPool.generated_pool.build do optional :gl_repository, :string, 6 optional :gl_project_path, :string, 8 end + add_message "gitaly.CommitTrailer" do + optional :key, :bytes, 1 + optional :value, :bytes, 2 + end add_message "gitaly.GitCommit" do optional :id, :string, 1 optional :subject, :bytes, 2 @@ -25,6 +29,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do optional :body_size, :int64, 7 optional :signature_type, :enum, 8, "gitaly.SignatureType" optional :tree_id, :string, 9 + repeated :trailers, :message, 10, "gitaly.CommitTrailer" end add_message "gitaly.CommitAuthor" do optional :name, :bytes, 1 @@ -81,6 +86,7 @@ end module Gitaly Repository = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.Repository").msgclass + CommitTrailer = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.CommitTrailer").msgclass GitCommit = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.GitCommit").msgclass CommitAuthor = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.CommitAuthor").msgclass ExitStatus = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.ExitStatus").msgclass diff --git a/ruby/spec/lib/gitlab/git/commit_spec.rb b/ruby/spec/lib/gitlab/git/commit_spec.rb index 0a69efb8ed..8db96eca7c 100644 --- a/ruby/spec/lib/gitlab/git/commit_spec.rb +++ b/ruby/spec/lib/gitlab/git/commit_spec.rb @@ -186,7 +186,8 @@ describe Gitlab::Git::Commit do committer_name: "Dmitriy Zaporozhets", id: SeedRepo::Commit::ID, message: "tree css fixes", - parent_ids: ["874797c3a73b60d2187ed6e2fcabd289ff75171e"] + parent_ids: ["874797c3a73b60d2187ed6e2fcabd289ff75171e"], + trailers: [] } end end -- GitLab