2 SGBlog -- Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
3 Copyright (C) 2020-2022 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/UCSPI blogging/phlogging/gemlogging engine
31 "github.com/go-git/go-git/v5"
32 "github.com/go-git/go-git/v5/plumbing"
33 "github.com/go-git/go-git/v5/plumbing/object"
34 "github.com/hjson/hjson-go"
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 initRepo(cfg *Cfg) (*plumbing.Hash, error) {
87 repo, err = git.PlainOpen(cfg.GitPath)
91 head, err := repo.Reference(plumbing.ReferenceName(cfg.Branch), false)
95 headHash := head.Hash()
96 if notes, err := repo.Notes(); err == nil {
97 var notesRef *plumbing.Reference
98 notes.ForEach(func(ref *plumbing.Reference) error {
99 switch string(ref.Name()) {
100 case "refs/notes/commits":
102 case cfg.CommentsNotesRef:
104 case cfg.TopicsNotesRef:
110 if commentsCommit, err := repo.CommitObject(notesRef.Hash()); err == nil {
111 notesTree, _ = commentsCommit.Tree()
114 if commentsRef != nil {
115 if commentsCommit, err := repo.CommitObject(commentsRef.Hash()); err == nil {
116 commentsTree, _ = commentsCommit.Tree()
119 if topicsRef != nil {
120 if topicsCommit, err := repo.CommitObject(topicsRef.Hash()); err == nil {
121 topicsTree, _ = topicsCommit.Tree()
125 return &headHash, nil
128 func readCfg(cfgPath string) (*Cfg, error) {
129 cfgRaw, err := os.ReadFile(cfgPath)
133 var cfgGeneral map[string]interface{}
134 if err = hjson.Unmarshal(cfgRaw, &cfgGeneral); err != nil {
137 cfgRaw, err = json.Marshal(cfgGeneral)
142 if err = json.Unmarshal(cfgRaw, &cfg); err != nil {
149 gopherCfgPath := flag.String("gopher", "", "Path to gopher-related configuration file")
150 geminiCfgPath := flag.String("gemini", "", "Path to gemini-related configuration file")
151 flag.Usage = func() {
152 fmt.Fprintf(flag.CommandLine.Output(), `Usage of sgblog:
153 sgblog -- run CGI HTTP backend
154 sgblog -gopher /path/to/cfg.hjson -- run UCSPI/inetd Gopher backend
155 sgblog -gemini /path/to/cfg.hjson -- run UCSPI+tlss Gemini backend
159 log.SetFlags(log.Lshortfile)
160 if *gopherCfgPath != "" {
161 serveGopher(*gopherCfgPath)
162 } else if *geminiCfgPath != "" {
163 serveGemini(*geminiCfgPath)