1 // SGBlog -- Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
2 // Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU Affero General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
26 "github.com/go-git/go-git/v5/plumbing"
27 "github.com/go-git/go-git/v5/plumbing/object"
28 "go.stargrave.org/sgblog"
31 type TopicsCache map[string][]plumbing.Hash
33 type TopicsCacheState struct {
38 func (tc TopicsCache) Topics() []string {
39 topics := make([]string, 0, len(tc))
41 topics = append(topics, t)
47 func getTopicsCache(cfg *Cfg, repoLog object.CommitIter) (TopicsCache, error) {
48 cache := TopicsCache(make(map[string][]plumbing.Hash))
49 if topicsTree == nil {
52 top := topicsRef.Hash()
54 if cfg.TopicsCachePath != "" {
55 fd, err := os.Open(cfg.TopicsCachePath)
59 dec := gob.NewDecoder(fd)
60 var cacheState TopicsCacheState
61 err = dec.Decode(&cacheState)
66 if cacheState.Top == top {
67 return cacheState.Cache, nil
73 commit, err := repoLog.Next()
77 for _, topic := range sgblog.ParseTopics(sgblog.GetNote(repo, topicsTree, commit.Hash)) {
78 cache[topic] = append(cache[topic], commit.Hash)
82 if cfg.TopicsCachePath != "" {
83 // Assume that probability of suffix collision is negligible
84 suffix := strconv.FormatInt(time.Now().UnixNano()+int64(os.Getpid()), 16)
85 tmpPath := cfg.TopicsCachePath + suffix
86 fd, err := os.OpenFile(
88 os.O_RDWR|os.O_CREATE|os.O_EXCL,
94 enc := gob.NewEncoder(fd)
95 err = enc.Encode(&TopicsCacheState{top, cache})
101 if err = fd.Sync(); err != nil {
106 if err = fd.Close(); err != nil {
110 if err = os.Rename(tmpPath, cfg.TopicsCachePath); err != nil {
119 type HashesIter struct {
120 hashes []plumbing.Hash
123 func (s *HashesIter) Next() (*object.Commit, error) {
124 if len(s.hashes) == 0 {
128 h, s.hashes = s.hashes[0], s.hashes[1:]
129 return repo.CommitObject(h)