scripts/
static/
templates/
.gitignore
30 B
.gitsigners
112 B
AGENTS.md
7.4 KiB
LICENSE
89 B
README.md
1.9 KiB
deploy
723 B
discuss.go
16.7 KiB
git.go
3.7 KiB
git_cli.go
16.3 KiB
git_http.go
2.5 KiB
go.mod
664 B
go.sum
5.2 KiB
handler.go
10.8 KiB
handler_test.go
72.1 KiB
highlight.go
5.3 KiB
main.go
5.7 KiB
template.go
8.6 KiB
watch
272 B
git.go
raw
| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "html/template" |
| 5 | "sort" |
| 6 | "strings" |
| 7 | "time" |
| 8 | ) |
| 9 | |
| 10 | type RepoInfo struct { |
| 11 | Name string |
| 12 | Path string |
| 13 | GitDir string |
| 14 | Description string |
| 15 | Owner string |
| 16 | LastUpdated time.Time |
| 17 | Git *gitCLIBackend |
| 18 | } |
| 19 | |
| 20 | type CommitInfo struct { |
| 21 | Hash string |
| 22 | ShortHash string |
| 23 | Subject string |
| 24 | Body string |
| 25 | Author string |
| 26 | AuthorEmail string |
| 27 | AuthorDate time.Time |
| 28 | Parents []string |
| 29 | } |
| 30 | |
| 31 | type TreeEntryInfo struct { |
| 32 | Name string |
| 33 | IsDir bool |
| 34 | Size int64 |
| 35 | Mode string |
| 36 | Hash string |
| 37 | } |
| 38 | |
| 39 | type BlobInfo struct { |
| 40 | Name string |
| 41 | Size int64 |
| 42 | IsBinary bool |
| 43 | Lines []string |
| 44 | Content string |
| 45 | } |
| 46 | |
| 47 | type RefInfo struct { |
| 48 | Name string |
| 49 | Hash string |
| 50 | ShortHash string |
| 51 | Subject string |
| 52 | Author string |
| 53 | Date time.Time |
| 54 | IsTag bool |
| 55 | } |
| 56 | |
| 57 | type DiffFile struct { |
| 58 | OldName string |
| 59 | NewName string |
| 60 | Status string |
| 61 | Hunks []DiffHunk |
| 62 | IsBinary bool |
| 63 | Stats DiffStats |
| 64 | } |
| 65 | |
| 66 | func diffFileName(file DiffFile) string { |
| 67 | if file.NewName != "" { |
| 68 | return file.NewName |
| 69 | } |
| 70 | return file.OldName |
| 71 | } |
| 72 | |
| 73 | type DiffStats struct { |
| 74 | Added int |
| 75 | Deleted int |
| 76 | } |
| 77 | |
| 78 | type DiffHunk struct { |
| 79 | Header string |
| 80 | Lines []DiffLine |
| 81 | } |
| 82 | |
| 83 | type DiffLine struct { |
| 84 | Type string // "context", "add", "del" |
| 85 | Content string |
| 86 | // Highlighted contains escaped, syntax-highlighted source. |
| 87 | Highlighted template.HTML |
| 88 | OldNum int |
| 89 | NewNum int |
| 90 | } |
| 91 | |
| 92 | type TreeNode struct { |
| 93 | Name string |
| 94 | IsDir bool |
| 95 | Size int64 |
| 96 | Path string |
| 97 | Depth int |
| 98 | IsOpen bool |
| 99 | IsActive bool |
| 100 | } |
| 101 | |
| 102 | const maxBlobDisplaySize = 1 << 20 // 1MB |
| 103 | const diffContextLines = 5 |
| 104 | const maxDiffFiles = 256 // max files rendered per commit |
| 105 | |
| 106 | func sortTreeEntries(entries []TreeEntryInfo) { |
| 107 | sort.Slice(entries, func(i, j int) bool { |
| 108 | if entries[i].IsDir != entries[j].IsDir { |
| 109 | return entries[i].IsDir |
| 110 | } |
| 111 | return entries[i].Name < entries[j].Name |
| 112 | }) |
| 113 | } |
| 114 | |
| 115 | // collapseContext takes a flat list of diff lines and splits them into |
| 116 | // hunks, keeping only diffContextLines of context around add/del lines. |
| 117 | func collapseContext(lines []DiffLine) []DiffHunk { |
| 118 | if len(lines) == 0 { |
| 119 | return nil |
| 120 | } |
| 121 | |
| 122 | // Mark which lines to keep: diffContextLines around each changed line. |
| 123 | keep := make([]bool, len(lines)) |
| 124 | for i, l := range lines { |
| 125 | if l.Type == "add" || l.Type == "del" { |
| 126 | start := i - diffContextLines |
| 127 | if start < 0 { |
| 128 | start = 0 |
| 129 | } |
| 130 | end := i + diffContextLines |
| 131 | if end >= len(lines) { |
| 132 | end = len(lines) - 1 |
| 133 | } |
| 134 | for j := start; j <= end; j++ { |
| 135 | keep[j] = true |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | // Build hunks from contiguous kept ranges. |
| 141 | var hunks []DiffHunk |
| 142 | var current *DiffHunk |
| 143 | for i, l := range lines { |
| 144 | if keep[i] { |
| 145 | if current == nil { |
| 146 | current = &DiffHunk{} |
| 147 | } |
| 148 | current.Lines = append(current.Lines, l) |
| 149 | } else { |
| 150 | if current != nil { |
| 151 | hunks = append(hunks, *current) |
| 152 | current = nil |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | if current != nil { |
| 157 | hunks = append(hunks, *current) |
| 158 | } |
| 159 | |
| 160 | return hunks |
| 161 | } |
| 162 | |
| 163 | var mimeTypes = map[string]string{ |
| 164 | ".txt": "text/plain; charset=utf-8", |
| 165 | ".md": "text/plain; charset=utf-8", |
| 166 | ".go": "text/plain; charset=utf-8", |
| 167 | ".rs": "text/plain; charset=utf-8", |
| 168 | ".py": "text/plain; charset=utf-8", |
| 169 | ".js": "text/plain; charset=utf-8", |
| 170 | ".ts": "text/plain; charset=utf-8", |
| 171 | ".c": "text/plain; charset=utf-8", |
| 172 | ".h": "text/plain; charset=utf-8", |
| 173 | ".css": "text/css; charset=utf-8", |
| 174 | ".html": "text/html; charset=utf-8", |
| 175 | ".json": "application/json", |
| 176 | ".xml": "application/xml", |
| 177 | ".png": "image/png", |
| 178 | ".jpg": "image/jpeg", |
| 179 | ".jpeg": "image/jpeg", |
| 180 | ".gif": "image/gif", |
| 181 | ".svg": "image/svg+xml", |
| 182 | ".pdf": "application/pdf", |
| 183 | } |
| 184 | |
| 185 | func mimeFromPath(path string) string { |
| 186 | idx := strings.LastIndex(path, ".") |
| 187 | if idx < 0 { |
| 188 | return "" |
| 189 | } |
| 190 | ext := strings.ToLower(path[idx:]) |
| 191 | if t, ok := mimeTypes[ext]; ok { |
| 192 | return t |
| 193 | } |
| 194 | return "" |
| 195 | } |