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