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", "&#013;")
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) &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"
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}