vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Open one file at a time on the commit page
628d45239eb09af4e46c9dc779d30144e96c7e6f · Keenan Nemetz · 11d ago · parent 816cc543
Open one file at a time on the commit page
A commit page rendered every patch it contained, so a broad commit turned one
page load into every blob it touched. It now opens the file tree beside the diff
of a single file, the same shape a change request's Files tab uses: the tree is
built from `git show --name-status`, only the selected path's patch is read, and
a tree click swaps the pane over HTMX. `/commit/{sha}/{path}` deep-links a file
and a 512 KiB cap still bounds one file's patch.
The file tree became one widget for three surfaces rather than two, so its
context gained a Commit field and its href/target/status rules moved onto the
view (Branch, Diff, Href).
The pane's header is now one row: the file's name on the left and its line
counts on the right, with the separate "1 file changed" summary line dropped
when a single file fills the pane. The total-line remains for the multi-file
diff a compare renders.
server/web/static/vole.css +32−19
| @@ -748,6 +748,9 @@ pre code { background: none; border: 0; padding: 0; font-size: inherit; } | |||
| 748 | 748 | background: var(--bg-panel); | |
| 749 | 749 | } | |
| 750 | 750 | .diff h3 { | |
| 751 | display: flex; | ||
| 752 | align-items: center; | ||
| 753 | gap: 8px; | ||
| 751 | 754 | margin: 0; | |
| 752 | 755 | padding: 8px 12px; | |
| 753 | 756 | background: var(--bg-raised); | |
| @@ -755,7 +758,29 @@ pre code { background: none; border: 0; padding: 0; font-size: inherit; } | |||
| 755 | 758 | font-size: calc(13.2px * var(--font-scale)); | |
| 756 | 759 | font-weight: 550; | |
| 757 | 760 | } | |
| 758 | .diff h3 .path { font-family: var(--mono); } | ||
| 761 | /* The name gives way before the counts do: the path can be long enough to cut, | ||
| 762 | and two short numbers at the end of the row are the reason the row exists. */ | ||
| 763 | .diff h3 .path { | ||
| 764 | flex: 1 1 auto; | ||
| 765 | min-width: 0; | ||
| 766 | overflow: hidden; | ||
| 767 | text-overflow: ellipsis; | ||
| 768 | white-space: nowrap; | ||
| 769 | font-family: var(--mono); | ||
| 770 | } | ||
| 771 | /* One file's counts, in the same row as its name and at the other end of it. */ | ||
| 772 | .diffstat { | ||
| 773 | display: inline-flex; | ||
| 774 | align-items: center; | ||
| 775 | gap: 8px; | ||
| 776 | flex: 0 0 auto; | ||
| 777 | font-family: var(--mono); | ||
| 778 | font-size: calc(12.1px * var(--font-scale)); | ||
| 779 | font-weight: 400; | ||
| 780 | } | ||
| 781 | /* The row's own gap spaces the badges; the label's standalone margin was for | ||
| 782 | prose, where nothing else spaced it. */ | ||
| 783 | .diff h3 .label { margin-left: 0; } | ||
| 759 | 784 | .diff table { | |
| 760 | 785 | font-family: var(--mono); | |
| 761 | 786 | font-size: calc(12.65px * var(--font-scale)); | |
| @@ -1168,24 +1193,12 @@ details[open] > summary .tree-chevron { transform: rotate(90deg); } | |||
| 1168 | 1193 | .review-mark[data-status="R"] { color: var(--accent); background: var(--accent-soft); } | |
| 1169 | 1194 | .review-mark[data-status="M"] { color: var(--warn); background: var(--warn-soft); } | |
| 1170 | 1195 | .review-main { min-width: 0; scroll-margin-top: 16px; } | |
| 1171 | /* The review pane opens with a bar the same height as the tree header, so both | ||
| 1172 | columns start on the same line instead of dropping by a summary paragraph. */ | ||
| 1173 | .review-main .diffsum { | ||
| 1174 | display: flex; | ||
| 1175 | align-items: center; | ||
| 1176 | height: 36px; | ||
| 1177 | margin: 0; | ||
| 1178 | padding: 0 10px; | ||
| 1179 | background: var(--bg-raised); | ||
| 1180 | border: 1px solid var(--border); | ||
| 1181 | border-radius: var(--radius-lg) var(--radius-lg) 0 0; | ||
| 1182 | font-size: calc(12.1px * var(--font-scale)); | ||
| 1183 | } | ||
| 1184 | .review-main .diff { | ||
| 1185 | margin-top: 0; | ||
| 1186 | border-top: 0; | ||
| 1187 | border-radius: 0 0 var(--radius-lg) var(--radius-lg); | ||
| 1188 | } | ||
| 1196 | /* A single file is the whole pane, so its header is the pane's top bar: the file | ||
| 1197 | at one end and its counts at the other, as tall as the tree header so both | ||
| 1198 | columns start on the same line. The extra pixel is the section's own top | ||
| 1199 | border, which the height must not add to. */ | ||
| 1200 | .review-main .diff { margin-top: 0; } | ||
| 1201 | .review-main .diff h3 { height: 35px; padding: 0 10px; } | ||
| 1189 | 1202 | ||
| 1190 | 1203 | .code-main .filebody { max-width: none; } | |
| 1191 | 1204 | .code-main .readme > *, | |