GitRoot
craft your forge, build your project, grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package main
6
7import (
8 "fmt"
9 "regexp"
10 "strings"
11 "time"
12
13 "gitroot.dev/libs/golang/plugin/model"
14)
15
16const NullCommitHash = "0000000000000000000000000000000000000000"
17
18type linkedCommit struct {
19 prev *linkedCommit
20 next *linkedCommit
21 hash string
22 parentHash string
23 commit *model.Commit
24 line string
25}
26
27type linkedCommits struct {
28 commit *linkedCommit
29 noSortedCommits []*linkedCommit
30}
31
32func (lc *linkedCommits) insert(linkedC *linkedCommit) {
33 if lc.commit == nil {
34 lc.commit = linkedC
35 return
36 }
37 found := lc.commit.find(linkedC.hash)
38 if found == nil {
39 fatherFound := lc.commit.find(linkedC.parentHash)
40 if fatherFound == nil {
41 childFound := lc.commit.findParent(linkedC.hash)
42 if childFound == nil {
43 if linkedC.parentHash == NullCommitHash {
44 var last *linkedCommit = nil
45 for n := lc.commit; n != nil; n = n.next {
46 last = n
47 }
48 if last != nil {
49 last.next = linkedC
50 linkedC.prev = last
51 return
52 }
53 }
54 linkedC.next = lc.commit
55 lc.commit.prev = linkedC
56 lc.commit = linkedC
57 lc.noSortedCommits = append(lc.noSortedCommits, linkedC)
58 return
59 }
60 lc.insertAfter(childFound, linkedC)
61 return
62 }
63 lc.commit = lc.insertBefore(linkedC, fatherFound)
64 return
65 }
66 if linkedC.commit != nil {
67 found.commit = linkedC.commit
68 found.parentHash = linkedC.commit.ParentHash
69 }
70}
71
72func (lc *linkedCommits) Insert(commit model.Commit) {
73 lc.insert(&linkedCommit{commit: &commit, hash: commit.Hash, parentHash: commit.ParentHash})
74}
75
76func (lc *linkedCommits) InsertLine(line string) {
77 re := regexp.MustCompile(`data-hash="([^"]*)"`)
78 match := re.FindStringSubmatch(line)
79 hash := match[1]
80
81 parentHash := ""
82 if strings.Contains(line, "data-parent-hash=\"") {
83 re := regexp.MustCompile(`data-parent-hash="([^"]*)"`)
84 match := re.FindStringSubmatch(line)
85 parentHash = match[1]
86 }
87 lc.insert(&linkedCommit{commit: nil, hash: hash, parentHash: parentHash, line: line})
88}
89
90func (lc *linkedCommits) insertBefore(current *linkedCommit, father *linkedCommit) *linkedCommit {
91 if father.prev != nil {
92 if father.prev.parentHash == current.parentHash { //if previous commit have same parentHash, user has force push
93 father.prev = nil
94 } else {
95 father.prev.next = current
96 }
97 }
98
99 current.prev = father.prev
100 current.next = father
101
102 father.prev = current
103
104 var newFirst *linkedCommit
105 for n := current; n != nil; n = n.prev {
106 newFirst = n
107 }
108 return newFirst
109}
110
111func (lc *linkedCommits) insertAfter(child *linkedCommit, current *linkedCommit) {
112 if child.next != nil {
113 child.next.prev = current
114 }
115
116 current.prev = child
117 current.next = child.next
118
119 child.parentHash = current.hash
120 child.next = current
121}
122
123func (lc *linkedCommit) find(commitHash string) *linkedCommit {
124 if lc.hash == commitHash {
125 return lc
126 } else if lc.next == nil {
127 return nil
128 } else {
129 return lc.next.find(commitHash)
130 }
131}
132
133func (lc *linkedCommit) findParent(parentHash string) *linkedCommit {
134 if lc.parentHash == parentHash {
135 return lc
136 } else if lc.next == nil {
137 return nil
138 } else {
139 return lc.next.findParent(parentHash)
140 }
141}
142
143func (lc *linkedCommits) hole() [][]string {
144 res := [][]string{}
145 for n := lc.commit; n != nil; n = n.next {
146 if n.commit != nil && n.next != nil && n.commit.ParentHash == n.next.hash {
147 continue
148 } else if n.commit != nil && n.next != nil {
149 res = append(res, []string{n.commit.ParentHash, n.next.hash})
150 } else if n.next != nil {
151 res = append(res, []string{n.hash, n.next.hash})
152 }
153 }
154 return res
155}
156
157func (lc *linkedCommits) Render() string {
158 res := strings.Builder{}
159 for n := lc.commit; n != nil; n = n.next {
160 res.WriteString(n.Render())
161 if n.next != nil && n.parentHash != n.next.hash {
162 res.WriteString((&linkedCommit{hash: n.parentHash}).Render())
163 }
164 }
165 return res.String()
166}
167
168func (commit *linkedCommit) Render() string {
169 if commit.commit != nil {
170 message := replaceEmoji(commit.commit.Message)
171 msg := strings.SplitN(message, "\n", 2)
172 comMsg := msg[0]
173
174 details := strings.ReplaceAll(strings.TrimSpace(message), "\n", "
")
175 return fmt.Sprintf(`<li data-hash="%s" data-parent-hash="%s"><code title="%s">%.*s</code> - <span title="%s">%s</span> <i style="font-size:small;float:right;">(%s) <%s></i></li>`, commit.hash, commit.commit.ParentHash, commit.hash, 7, commit.hash, details, comMsg, commit.commit.Date.Format(time.ANSIC), commit.commit.Committer) + "\n"
176 }
177 if commit.line != "" {
178 return commit.line + "\n"
179 }
180 return fmt.Sprintf("<li data-hash=\"%s\"><code title=\"%s\">%.*s</code> - <i>commit not found</i></li>\n", commit.hash, commit.hash, 7, commit.hash)
181}