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", "&#013;")
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) &lt;%s&gt;</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}