2 SGBlog -- Git-backed CGI/inetd blogging/phlogging engine
3 Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as
7 published by the Free Software Foundation, version 3 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU Affero General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 // Git-backed CGI/inetd blogging/phlogging engine
32 "github.com/go-git/go-git/v5"
33 "github.com/go-git/go-git/v5/plumbing"
34 "github.com/go-git/go-git/v5/plumbing/object"
42 sha1DigestRe = regexp.MustCompilePOSIX(fmt.Sprintf("([0-9a-f]{%d,%d})", sha1.Size*2, sha1.Size*2))
44 notesTree *object.Tree
45 commentsRef *plumbing.Reference
46 commentsTree *object.Tree
47 topicsRef *plumbing.Reference
48 topicsTree *object.Tree
67 CommentsNotesRef string
71 TopicsCachePath string
76 func msgSplit(msg string) []string {
77 lines := strings.Split(msg, "\n")
78 lines = lines[:len(lines)-1]
80 lines = []string{lines[0], "", ""}
85 func getNote(tree *object.Tree, what plumbing.Hash) []byte {
89 var entry *object.TreeEntry
91 paths := make([]string, 3)
92 paths[0] = what.String()
93 paths[1] = paths[0][:2] + "/" + paths[0][2:]
94 paths[2] = paths[1][:4+1] + "/" + paths[1][4+1:]
95 for _, p := range paths {
96 entry, err = tree.FindEntry(p)
104 blob, err := repo.BlobObject(entry.Hash)
108 r, err := blob.Reader()
112 data, err := ioutil.ReadAll(r)
116 return bytes.TrimSuffix(data, []byte{'\n'})
119 func parseComments(data []byte) []string {
120 comments := []string{}
122 comment := make([]string, 0, 4)
123 lines := strings.Split(strings.TrimSuffix(string(data), "\n"), "\n")
127 for _, s := range lines {
129 comments = append(comments, strings.Join(comment, "\n"))
130 comment = make([]string, 0, 4)
136 comment = append(comment, "")
141 comment = append(comment, "")
143 comment = append(comment, strings.TrimPrefix(s, "+ "))
147 comment = append(comment, s)
149 if len(comment) > 1 {
150 comments = append(comments, strings.Join(comment, "\n"))
155 func parseTopics(data []byte) []string {
156 var s scanner.Scanner
157 s.Init(bytes.NewBuffer(data))
159 for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
160 topics = append(topics, s.TokenText())
166 func initRepo(cfg *Cfg) (*plumbing.Hash, error) {
168 repo, err = git.PlainOpen(cfg.GitPath)
172 head, err := repo.Reference(plumbing.ReferenceName(cfg.Branch), false)
176 headHash := head.Hash()
177 if notes, err := repo.Notes(); err == nil {
178 var notesRef *plumbing.Reference
179 notes.ForEach(func(ref *plumbing.Reference) error {
180 switch string(ref.Name()) {
181 case "refs/notes/commits":
183 case cfg.CommentsNotesRef:
185 case cfg.TopicsNotesRef:
191 if commentsCommit, err := repo.CommitObject(notesRef.Hash()); err == nil {
192 notesTree, _ = commentsCommit.Tree()
195 if commentsRef != nil {
196 if commentsCommit, err := repo.CommitObject(commentsRef.Hash()); err == nil {
197 commentsTree, _ = commentsCommit.Tree()
200 if topicsRef != nil {
201 if topicsCommit, err := repo.CommitObject(topicsRef.Hash()); err == nil {
202 topicsTree, _ = topicsCommit.Tree()
206 return &headHash, nil
210 if len(os.Args) == 3 && os.Args[1] == "-gopher" {