1// Copyright 2020 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package git
5
6import (
7 "bufio"
8 "bytes"
9 "io"
10 "strings"
11)
12
13// CommitFromReader will generate a Commit from a provided reader
14// We need this to interpret commits from cat-file or cat-file --batch
15//
16// If used as part of a cat-file --batch stream you need to limit the reader to the correct size
17func CommitFromReader(gitRepo *Repository, objectID ObjectID, reader io.Reader) (*Commit, error) {
18 commit := &Commit{
19 ID: objectID,
20 Author: &Signature{},
21 Committer: &Signature{},
22 }
23
24 payloadSB := new(strings.Builder)
25 signatureSB := new(strings.Builder)
26 messageSB := new(strings.Builder)
27 message := false
28 pgpsig := false
29
30 bufReader, ok := reader.(*bufio.Reader)
31 if !ok {
32 bufReader = bufio.NewReader(reader)
33 }
34
35readLoop:
36 for {
37 line, err := bufReader.ReadBytes('\n')
38 if err != nil {
39 if err == io.EOF {
40 if message {
41 _, _ = messageSB.Write(line)
42 }
43 _, _ = payloadSB.Write(line)
44 break readLoop
45 }
46 return nil, err
47 }
48 if pgpsig {
49 if len(line) > 0 && line[0] == ' ' {
50 _, _ = signatureSB.Write(line[1:])
51 continue
52 }
53 pgpsig = false
54 }
55
56 if !message {
57 // This is probably not correct but is copied from go-gits interpretation...
58 trimmed := bytes.TrimSpace(line)
59 if len(trimmed) == 0 {
60 message = true
61 _, _ = payloadSB.Write(line)
62 continue
63 }
64
65 split := bytes.SplitN(trimmed, []byte{' '}, 2)
66 var data []byte
67 if len(split) > 1 {
68 data = split[1]
69 }
70
71 switch string(split[0]) {
72 case "tree":
73 commit.Tree = *NewTree(gitRepo, MustIDFromString(string(data)))
74 _, _ = payloadSB.Write(line)
75 case "parent":
76 commit.Parents = append(commit.Parents, MustIDFromString(string(data)))
77 _, _ = payloadSB.Write(line)
78 case "author":
79 commit.Author = &Signature{}
80 commit.Author.Decode(data)
81 _, _ = payloadSB.Write(line)
82 case "committer":
83 commit.Committer = &Signature{}
84 commit.Committer.Decode(data)
85 _, _ = payloadSB.Write(line)
86 case "encoding":
87 _, _ = payloadSB.Write(line)
88 case "gpgsig":
89 fallthrough
90 case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
91 _, _ = signatureSB.Write(data)
92 _ = signatureSB.WriteByte('\n')
93 pgpsig = true
94 }
95 } else {
96 _, _ = messageSB.Write(line)
97 _, _ = payloadSB.Write(line)
98 }
99 }
100 commit.CommitMessage = messageSB.String()
101 commit.Signature = &ObjectSignature{
102 Signature: signatureSB.String(),
103 Payload: payloadSB.String(),
104 }
105 if len(commit.Signature.Signature) == 0 {
106 commit.Signature = nil
107 }
108
109 return commit, nil
110}