Documentation
¶
Overview ¶
Package gitdiff shells out to the git CLI to enumerate changed files and load per-file unified diffs for prereview. Shelling out (vs. a Go git library) is deliberate — it respects the user's local git config (rename detection thresholds, autocrlf, etc.) so what prereview shows matches what `git diff` shows in their terminal.
Index ¶
- Variables
- func ChangedCount(entries []FileEntry) int
- func CurrentBranch(repo string) string
- func HighlightLine(filename, content string) template.HTML
- func HighlightLines(filename string, contents []string) []template.HTML
- func IsMarkdownPath(path string) bool
- func IsValidRef(repo, ref string) bool
- func ListBranches(repo string) []string
- func ListRemoteBranches(repo string) []string
- type DiffLine
- type FileDiff
- type FileEntry
- type MarkdownBlock
Constants ¶
This section is empty.
Variables ¶
var HighlightCSS = func() string { var buf bytes.Buffer if err := chromaFormatter.WriteCSS(&buf, chromaStyle); err != nil { return "" } return buf.String() }()
HighlightCSS returns the chroma stylesheet for the active style. Computed once at package-init time; main.go serves it as /syntax.css.
Functions ¶
func ChangedCount ¶
ChangedCount returns how many entries have a non-empty Status — i.e. how many of these files differ from the active base. Useful for the drawer header ("N files · M changed").
func CurrentBranch ¶
CurrentBranch returns the short name of the checked-out branch (e.g. "main", "feature/foo"). Returns "HEAD" in a detached state (which is what `git rev-parse --abbrev-ref HEAD` emits there). Errors return an empty string — callers should display "current branch" or similar generic copy when the result is blank.
func HighlightLine ¶
HighlightLine is retained for callers that want to highlight a single line in isolation (currently none in the project — kept for backward compatibility and potential ad-hoc use). For bulk highlighting use HighlightLines, which amortises tokenizer startup across all lines.
func HighlightLines ¶
HighlightLines tokenises every line of `contents` in a single pass and returns the syntax-highlighted HTML per line. Falls back to per-line HTML escape on tokenizer errors. Empty input returns nil.
Joining with '\n' before tokenising lets chroma understand line transitions naturally — string tokens that span lines keep their type, etc. The output is then split back into per-line groups by walking the token stream and splitting tokens on their '\n' bytes.
func IsMarkdownPath ¶
IsMarkdownPath reports whether path is a Markdown file by extension.
func IsValidRef ¶
IsValidRef reports whether `ref` resolves to a commit in this repo. Used by the runtime base picker to validate user-typed refs before swapping state.Base. Treats both branch names and commit-ish refs (HEAD~3, abc1234) uniformly via `git rev-parse --verify`.
func ListBranches ¶
ListBranches returns the short names of every local branch in the repo, sorted alphabetically. Used to populate the base-picker dropdown — `git for-each-ref` runs in microseconds even on big repos, so calling it from every Mount is fine.
func ListRemoteBranches ¶
ListRemoteBranches returns the short names of every remote-tracking branch (e.g. "origin/main"), sorted. The symbolic "<remote>/HEAD" entries are skipped — they're aliases, not useful review bases. Lets the base picker offer "review since I branched off origin/main" without a freeform input.
Types ¶
type DiffLine ¶
type DiffLine struct {
OldNum int // 0 when the line doesn't exist on the old side
NewNum int // 0 when the line doesn't exist on the new side
Kind string // "add" | "del" | "ctx"
Content string // raw line text, no leading +/-/space, no trailing \n
// HighlightedContent is Content rendered as syntax-highlighted HTML
// spans via chroma. Templates render this instead of Content; the
// raw Content stays around for CSV exports and other consumers that
// don't want HTML markup.
HighlightedContent template.HTML
}
DiffLine is one rendered line in the viewer. Exactly one of OldNum / NewNum may be zero (for additions and deletions respectively); for context lines (Kind == "ctx") both are populated.
func CollapseToHunks ¶
CollapseToHunks turns a full-file line list (every line tagged add/del/ctx, as LoadDiff produces with -U999999) into a true diff view: only changed lines plus `ctx` unchanged lines on each side of a change. Each maximal run of dropped unchanged lines is replaced by a single synthetic fold line:
Kind == "fold", Content == "<N>" (N = number of skipped lines)
The fold line's OldNum/NewNum are set to the first skipped line's numbers so the template can derive a stable, collision-free data-key (gaps never overlap).
If the input contains no add/del line (an unchanged file — shown via the all-files scope toggle or the clean-tree fallback) there is no diff to collapse, so the input is returned unchanged. Callers treat that as "show the whole file".
type FileDiff ¶
type FileDiff struct {
Path string
Lines []DiffLine
IsBinary bool // true when git reports "Binary files differ" — Lines is nil
Note string // e.g. "file added", "file deleted"; informational, may be empty
// MaxLineChars is the longest Content length (in characters) across
// all DiffLines. Used by the template to set `.code` width in `ch`
// units up front; without this hint, the browser would compute
// `width: max-content` for every line-row to determine the diff
// container's scrollable extent — expensive on big files (950+
// rows) and the dominant cost in perceived vertical-scroll lag.
MaxLineChars int
// MarkdownBlocks is the rendered-Markdown view of the current
// (new-side) file: top-level blocks tagged with their source line
// range. Populated only for .md/.markdown paths; nil otherwise.
// Computed once per load and cached with the FileDiff.
MarkdownBlocks []MarkdownBlock
}
FileDiff is the full-file, line-by-line diff for one path.
func LoadDiff ¶
LoadDiff returns the full-file diff for one path against base.
Uses `git diff --no-color -U999999` so every line of the file appears (additions, deletions, context). For files that are pure additions (A) or pure deletions (D) git produces a diff against /dev/null, which the parser handles naturally — every line is "add" or "del" respectively.
Files larger than maxRenderableFileBytes short-circuit with a Note instead of loading content — the viewer renders the note and skips the per-line markup entirely.
func LoadDiffNoGit ¶ added in v0.1.0
LoadDiffNoGit returns the full-file view for one path when there is no git base to diff against — the sibling of LoadDiff. Every line is an "add" (the file is wholly "new" with no base), produced by the same loadUntrackedAsAdded reader git mode already uses for untracked files, then run through the shared highlightLines hook so syntax highlighting and rendered-Markdown blocks populate identically to the git path. Files over the render cap short-circuit with a Note, exactly like LoadDiff, so a huge file never gets read into the page.
type FileEntry ¶
type FileEntry struct {
Path string // post-rename / current path
Status string // "A","M","D","R","C","T","U"
Renamed string // pre-rename path for R/C; empty otherwise
Added int // lines added vs base; -1 means "binary / unknown"
Deleted int // lines deleted vs base; -1 means "binary / unknown"
CommentCount int // populated by the controller, not by gitdiff
}
FileEntry is one row in the changed-files list.
func ListFiles ¶
ListFiles enumerates every file currently in the working tree (tracked + untracked-non-ignored), annotated with diff information vs base where applicable.
This is the foundation of "show all files; diff is an overlay": prereview now always lets the reviewer browse the whole repo, with diff metadata layered on top of the files that happen to differ from base. Files unchanged vs base appear in the list with Status="", Added=0, Deleted=0 — the template renders them without a diff badge.
Deleted files (present in base, absent in the working tree) are intentionally omitted — they don't exist in the repo "right now". Reviewers who care about deletions can flip the base or read git history directly.
base may be "HEAD", a branch name, or any revision spec git accepts. If base is empty, diff annotation is skipped and every entry is bare.
func ListFilesNoGit ¶ added in v0.1.0
ListFilesNoGit enumerates the reviewable files when the target is NOT a git repo — the sibling of ListFiles for loose files / non-git directories.
- single != "" → exactly one entry (single-file review). single is the basename relative to repo (repo is its parent directory).
- single == "" → walk repo recursively. Skips the .git/ and .prereview/ control dirs, dotfiles and dotdirs (editor/state cruft a non-git tree has no .gitignore to hide), and files over the render cap (reviewing megabytes in a browser is pointless and would only render a "too large" placeholder anyway).
Every entry is Status "A": with no base there is no diff, so the whole file is "new" — the same convention ListFiles uses for untracked files and the empty-tree (clean-tree whole-branch) review. Added carries the line count so the drawer still shows a "+N" badge; binary files report 0 (countLines can't read them) and render as a binary placeholder when opened. Entries are sorted by path so the drawer is stable.
type MarkdownBlock ¶
MarkdownBlock is one independently-commentable rendered unit plus the 1-based, inclusive SOURCE line range it came from. The line range is what makes rendered-Markdown commenting line-accurate: a comment placed on a unit anchors to these real source lines, so it round-trips with the raw line view and the CSV — nothing is renumbered.
Single-line nodes (heading, code fence, blockquote, …) are one block. Containers are descended ONE level so review comments can target a single line: a list yields one block per list item, a table one block for the header row plus one per body row. A paragraph that spans multiple SOURCE lines is split into one block per source line ONLY when it is authored one-sentence-per-line (every line except possibly the last ends a sentence) — CommonMark soft-wraps such a paragraph into one <p>, and splitting restores per-line commenting. A hard-wrapped paragraph (lines break mid-sentence) instead renders as a single reflowed CommonMark paragraph, so a sentence is never broken across visual lines; it stays commentable at paragraph granularity (its wrap points are arbitrary, so per-line comments there would be meaningless). An inline span that crosses a soft line-break degrades to literal text on that one line; it never produces invalid HTML.
func RenderMarkdownBlocks ¶
func RenderMarkdownBlocks(src []byte) []MarkdownBlock
RenderMarkdownBlocks parses src and returns each commentable unit as safe HTML tagged with its source line span. Empty input → nil.