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 println("insert commit " + commit.Hash)
74 lc.insert(&linkedCommit{commit: &commit, hash: commit.Hash, parentHash: commit.ParentHash})
75 println("after:")
76 for n := lc.commit; n != nil; n = n.next {
77 nh := "null"
78 if n.next != nil {
79 nh = n.next.hash
80 }
81 println("\th=" + n.hash + " ph=" + n.parentHash + " nh=" + nh)
82 }
83}
84
85func (lc *linkedCommits) InsertLine(line string) {
86 re := regexp.MustCompile(`data-hash="([^"]*)"`)
87 match := re.FindStringSubmatch(line)
88 hash := match[1]
89
90 parentHash := ""
91 if strings.Contains(line, "data-parent-hash=\"") {
92 re := regexp.MustCompile(`data-parent-hash="([^"]*)"`)
93 match := re.FindStringSubmatch(line)
94 parentHash = match[1]
95 }
96 println("insert line " + hash)
97 lc.insert(&linkedCommit{commit: nil, hash: hash, parentHash: parentHash, line: line})
98 println("after:")
99 for n := lc.commit; n != nil; n = n.next {
100 nh := "null"
101 if n.next != nil {
102 nh = n.next.hash
103 }
104 println("\th=" + n.hash + " ph=" + n.parentHash + " nh=" + nh)
105 }
106}
107
108func (lc *linkedCommits) insertBefore(current *linkedCommit, father *linkedCommit) *linkedCommit {
109 if father.prev != nil {
110 if father.prev.parentHash == current.parentHash { //if previous commit have same parentHash, user has force push
111 father.prev = nil
112 } else {
113 father.prev.next = current
114 }
115 }
116
117 current.prev = father.prev
118 current.next = father
119
120 father.prev = current
121
122 var newFirst *linkedCommit
123 for n := current; n != nil; n = n.prev {
124 newFirst = n
125 }
126 return newFirst
127}
128
129func (lc *linkedCommits) insertAfter(child *linkedCommit, current *linkedCommit) {
130 if child.next != nil {
131 child.next.prev = current
132 }
133
134 current.prev = child
135 current.next = child.next
136
137 child.parentHash = current.hash
138 child.next = current
139}
140
141func (lc *linkedCommit) find(commitHash string) *linkedCommit {
142 if lc.hash == commitHash {
143 return lc
144 } else if lc.next == nil {
145 return nil
146 } else {
147 return lc.next.find(commitHash)
148 }
149}
150
151func (lc *linkedCommit) findParent(parentHash string) *linkedCommit {
152 if lc.parentHash == parentHash {
153 return lc
154 } else if lc.next == nil {
155 return nil
156 } else {
157 return lc.next.findParent(parentHash)
158 }
159}
160
161func (lc *linkedCommits) hole() [][]string {
162 res := [][]string{}
163 for n := lc.commit; n != nil; n = n.next {
164 if n.commit != nil && n.next != nil && n.commit.ParentHash == n.next.hash {
165 continue
166 } else if n.commit != nil && n.next != nil {
167 res = append(res, []string{n.commit.ParentHash, n.next.hash})
168 } else if n.next != nil {
169 res = append(res, []string{n.hash, n.next.hash})
170 }
171 }
172 return res
173}
174
175func (lc *linkedCommits) Render() string {
176 res := strings.Builder{}
177 for n := lc.commit; n != nil; n = n.next {
178 res.WriteString(n.Render())
179 if n.next != nil && n.parentHash != n.next.hash {
180 res.WriteString((&linkedCommit{hash: n.parentHash}).Render())
181 }
182 }
183 return res.String()
184}
185
186func (commit *linkedCommit) Render() string {
187 if commit.commit != nil {
188 message := replaceEmoji(commit.commit.Message)
189 msg := strings.SplitN(message, "\n", 2)
190 comMsg := msg[0]
191
192 details := strings.ReplaceAll(strings.TrimSpace(message), "\n", "
")
193 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"
194 }
195 if commit.line != "" {
196 return commit.line + "\n"
197 }
198 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)
199}