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