review

package module
v0.0.0-...-fa032f6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 20, 2026 License: MIT Imports: 15 Imported by: 0

README

review

review is a CLI and Go library that does the first pass of a code review without a model. It groups the net diff between two revisions and describes each group's additions, removals and changes in plain sentences. The result depends on the two endpoint trees, regardless of how the changes were split into commits. Mechanical edits are collapsed, and files that are safe to skim are listed separately. People and coding agents read the same output; an agent can run review overview, then review show <n> for one group's diff at a time.

brief reports the project's toolchain (languages, package managers, test runners, linters) and brief diff narrows that to what a branch touches, so run it alongside review, which describes the change itself:

brief diff main
review overview main

Installation

Comparing revisions requires Git 2.41 or later, which supports reading attributes from a selected revision.

Install the CLI with go install, or add the library with go get:

go install github.com/git-pkgs/review/cmd/review@latest
go get github.com/git-pkgs/review

Usage

$ review overview main
Branch topic
main...HEAD (2c6fe577e4c3..211b6441cd63), 3 files, +13 -2

Read in this order

1. Changes to lib/parse.go and tests  (2 files, +12 -1)
   Adds lib/parse_test.go. Changes the body of function `Parse`. Includes 1 test file.
     M lib/parse.go       +3 -1
     A lib/parse_test.go  +9 -0


Also changed

2. Documentation  (1 file, +1 -1)
   Changes docs/usage.md (# Usage).

Diffs use the selected head revision's .gitattributes, including when comparing older commits. Local attribute overrides do not affect line counts, hunk analysis or show output.

review show <n> prints the diff for one group:

review show 1 main

Files are grouped by links between their changed code and by test names. A changed call site and the changed declaration it uses go in one group, and each test goes with the file it is named after. Remaining files are grouped by directory. Groups are named after their files, with larger changes first; parts of a split group that other parts depend on are listed before their callers. Commit messages and intermediate edits do not affect grouping.

Tests are matched to the file they're named after across directories: foo_test.go, foo.test.ts, test_foo.py, FooTest.java and foo_spec.rb all map to foo. When several files are called foo, the test is paired with the one whose directory path best matches its own, so spec/models/user_spec.rb is paired with app/models/user.rb over app/serializers/user.rb. On a tie, the test is grouped by its own directory.

A group of more than 12 files is split into parts by directory, numbered like 3.1 and 3.2 (which review show 3.2 accepts), at the first level where at least two directories hold three or more of its files. Each test is placed in the same part as the file it tests; remaining files are listed as "Other files".

Group descriptions are built from an outline graph of the changed files at the head commit, which maps each hunk to the declarations it touches, and from a comparison of outlines of both versions of each file. roles classifies what each file is for and manifests identifies dependency files. Documentation, CI config, build files and dependency manifests are listed under "Also changed". Pure renames, whitespace-only edits, an identical edit repeated across files, and a repeated edit with different string or number values in three or more files (such as translations of new keys) are listed under "Mechanical changes". Whitespace-only means the same words in the same order; in Python, YAML, Makefiles and other files where indentation is syntax, the edit is limited to trailing spaces and blank lines. Generated files, vendored files, binaries and lockfiles are listed under "Safe to skim".

Ranges use git's syntax:

review overview              default branch...HEAD
review overview main         main...HEAD
review overview main...topic topic against its merge base with main
review overview main..topic  topic against main directly
review overview -C ../repo   run in another repository
review overview -v           list declaration changes under each file
review overview --format markdown  for pasting into a pull request
review overview --json       everything, including roles evidence and links between files

To review a standalone net diff, pass it with --patch, as a file or as - for stdin. It accepts git diff and diff -u output, downloaded GitHub .diff files, and a single git format-patch message. Multi-commit patch series are rejected; generate the end diff with git diff base...HEAD instead. A patch contains only the changed lines, so in patch mode modified files are described by their hunk context and their roles are inferred from the path, while added and deleted files are described by their declarations:

gh pr diff 123 | review overview --patch -
gh pr diff 123 | review show --patch - 2
git diff main...HEAD | review overview --patch -

Library

The review package takes diffs and file contents as values, so it runs in WebAssembly or inside a service. Pass each file through Analyze and add the result to an Overview, then call Group:

o := &review.Overview{Links: links}
for _, d := range diffs {
	f, err := review.Analyze(d)
	if err != nil {
		return err
	}
	o.Add(f)
}
o.Group()
o.Render(os.Stdout, review.TextOptions{Markdown: true})

A FileDiff contains the path, status, counts and hunks. Its Base and Head contents, Symbols and BaseSymbols, and the overview's Links between files are optional. With contents, declarations are compared between versions; with symbols, each hunk is labelled with the declarations it changed; with links, files are grouped and ordered by their dependencies. ReadPatch parses one unified diff into file sections and returns any single-message format-patch metadata. FromPatch runs the full parse, analyse and group sequence. WritePatchFiles writes the original sections for selected paths. Commit metadata does not affect grouping or rendered output.

The gitdiff package asks Git for the net diff between two revisions and fills in contents, symbols and links. It builds the head graph from the changed files, plus the go.mod and __init__.py files above them, written from the stored blobs. A second graph supplies base declarations for comparison. Unchanged source files are left out to limit parsing work:

opts, _ := gitdiff.ParseRange([]string{"main"})
o, err := gitdiff.Build(ctx, ".", opts)

Limitations

In a repository, review compares commits: staged and unstaged edits in the working tree are outside any range it reads, so commit them, or pass git diff output with --patch, to review them.

Whitespace inside string literals counts as whitespace, so "a b" becoming "a b" is reported as a whitespace-only edit.

outline resolves imports between files for Go, Python and Ruby. JavaScript and TypeScript changes are grouped by test name and directory, and their summaries name declarations within each file.

License

MIT.

Documentation

Overview

Package review breaks a change down for a reviewer: what each changed file is for, which files are safe to skim, and which declarations changed. It works from diffs and file contents supplied by the caller and never runs git; see the gitdiff package for reading a range from a repository.

Index

Constants

View Source
const (
	GroupChange       = "change"
	GroupDocs         = "docs"
	GroupConfig       = "config"
	GroupDependencies = "dependencies"
	GroupMechanical   = "mechanical"
	GroupSkim         = "skim"
)

Group kinds, in the order they are presented.

View Source
const (
	Added       = "added"
	Modified    = "modified"
	Deleted     = "deleted"
	Renamed     = "renamed"
	Copied      = "copied"
	TypeChanged = "type-changed"
)

File statuses.

View Source
const (
	SymbolAdded     = "added"
	SymbolSignature = "signature"
	SymbolBody      = "body"
)

Symbol change kinds.

View Source
const MaxContentBytes = 1 << 20

MaxContentBytes is the most file content worth supplying per side. Larger files are still classified from their first bytes but not outlined.

Variables

This section is empty.

Functions

func WritePatchFiles

func WritePatchFiles(w io.Writer, r io.Reader, paths []string) error

WritePatchFiles copies each selected file's sections exactly as written. Paths use the names FromPatch reports, including the new names of renames.

Types

type Commit

type Commit struct {
	SHA     string       `json:"sha,omitempty"`
	Subject string       `json:"subject"`
	Body    string       `json:"body,omitempty"`
	Files   []CommitFile `json:"files,omitempty"`
}

Commit is one commit in the change, oldest first.

func ReadPatch

func ReadPatch(r io.Reader, visit func(FileDiff) error) ([]Commit, error)

ReadPatch parses a single diff as written by git diff, git format-patch or diff -u, calling visit once per section, and returns optional format-patch metadata. Patch series are rejected. Added and deleted files carry their full contents, since the diff holds every line; other files carry none.

func ReadPatchSections

func ReadPatchSections(r io.Reader, visit func(d FileDiff, section string) error) ([]Commit, error)

ReadPatchSections is ReadPatch that also passes each file's section of the patch exactly as written, for showing it again unchanged.

type CommitFile

type CommitFile struct {
	Path      string `json:"path"`
	OldPath   string `json:"old_path,omitempty"`
	Additions int    `json:"additions"`
	Deletions int    `json:"deletions"`
}

CommitFile is one path a commit changed and by how much. OldPath is set when the commit renamed it.

type Content

type Content struct {
	Data     []byte
	Complete bool
}

Content is one side of a changed file. Complete is false when Data holds only a prefix.

type Dependency

type Dependency struct {
	Ecosystem string `json:"ecosystem"`
	Kind      string `json:"kind"`
}

Dependency marks a file a package manager reads, as identified by manifests.

type File

type File struct {
	Path       string           `json:"path"`
	OldPath    string           `json:"old_path,omitempty"`
	Status     string           `json:"status"`
	Similarity int              `json:"similarity,omitempty"`
	Additions  int              `json:"additions"`
	Deletions  int              `json:"deletions"`
	Binary     bool             `json:"binary,omitempty"`
	OldMode    string           `json:"old_mode,omitempty"`
	NewMode    string           `json:"new_mode,omitempty"`
	Roles      []roles.Role     `json:"roles"`
	Evidence   []roles.Evidence `json:"evidence"`
	Hunks      int              `json:"hunks"`
	Contexts   []string         `json:"contexts,omitempty"`
	Dependency *Dependency      `json:"dependency,omitempty"`
	Skip       string           `json:"skip,omitempty"`
	Mechanical string           `json:"mechanical,omitempty"`
	Surface    *Surface         `json:"surface,omitempty"`
	Symbols    []SymbolChange   `json:"symbols,omitempty"`
	// contains filtered or unexported fields
}

File is the analysis of one changed path.

func Analyze

func Analyze(d FileDiff) (File, error)

Analyze classifies one changed file and compares its declarations when both sides' contents are available.

type FileDiff

type FileDiff struct {
	Path       string
	OldPath    string
	Status     string
	Similarity int
	Additions  int
	Deletions  int
	Binary     bool
	Symlink    bool
	Submodule  bool
	// OldMode and NewMode are git file modes such as 100644, empty for a
	// side that doesn't exist or when the diff doesn't say.
	OldMode string
	NewMode string
	Hunks   []Hunk
	Base    *Content
	Head    *Content
	Symbols []Symbol
	// BaseSymbols are the declarations in the base version.
	BaseSymbols []Symbol
}

FileDiff is one changed file as a diff describes it. Base, Head, Symbols and BaseSymbols are optional: without contents, classification is by path alone and declarations are not compared; without Symbols, changes are described by hunk context rather than by name; without BaseSymbols, a declaration whose every line changed is taken to be new.

type Group

type Group struct {
	ID        int      `json:"id"`
	Kind      string   `json:"kind"`
	Title     string   `json:"title"`
	Summary   []string `json:"summary"`
	Commits   []string `json:"commits,omitempty"`
	Files     []string `json:"files"`
	Additions int      `json:"additions"`
	Deletions int      `json:"deletions"`
	// Children split a large group into parts, numbered from 1 within it.
	Children []Group `json:"children,omitempty"`
	// contains filtered or unexported fields
}

Group is a set of files to read together. IDs follow presentation order and are stable for the same input.

type Hunk

type Hunk struct {
	Header   string   `json:"header"`
	OldStart int      `json:"old_start"`
	OldLines int      `json:"old_lines"`
	NewStart int      `json:"new_start"`
	NewLines int      `json:"new_lines"`
	Lines    []string `json:"-"`
}

Hunk is one hunk of a file's diff. Lines keep their leading ' ', '-' or '+', and a "\ No newline at end of file" marker stays after the line it applies to.

func ParseHunkHeader

func ParseHunkHeader(header string) (Hunk, error)

ParseHunkHeader reads the line ranges from "@@ -a,b +c,d @@ context".

type Link struct {
	From string `json:"from"`
	To   string `json:"to"`
}

Link records that changed code in From uses changed code in To.

type Overview

type Overview struct {
	Range  *Range `json:"range,omitempty"`
	Branch string `json:"branch,omitempty"`
	// Commits is optional metadata; grouping and rendering use the net diff.
	Commits   []Commit `json:"commits,omitempty"`
	Files     []File   `json:"files"`
	Links     []Link   `json:"links,omitempty"`
	Groups    []Group  `json:"groups"`
	Additions int      `json:"additions"`
	Deletions int      `json:"deletions"`
	Warnings  []string `json:"warnings,omitempty"`
}

Overview is the breakdown of one net diff. Callers fill Range, Branch and Links as available, Add each file, then call Group.

func FromPatch

func FromPatch(r io.Reader) (*Overview, error)

FromPatch analyses and groups a single unified diff between two trees.

func (*Overview) Add

func (o *Overview) Add(f File)

Add appends an analysed file and updates the totals.

func (*Overview) Group

func (o *Overview) Group()

Group partitions the files into groups in reading order, replacing any earlier grouping.

func (*Overview) Render

func (o *Overview) Render(w io.Writer, opts TextOptions) error

Render writes the overview for a reader: what the change is, its groups in reading order, then what can be skimmed.

func (*Overview) Text

func (o *Overview) Text(w io.Writer) error

Text writes the overview as terminal text.

type Range

type Range struct {
	Base      string `json:"base"`
	Head      string `json:"head"`
	BaseRev   string `json:"base_rev"`
	HeadRev   string `json:"head_rev"`
	MergeBase bool   `json:"merge_base"`
}

Range is the pair of commits a review covers.

type Surface

type Surface struct {
	Lines     []SurfaceLine `json:"lines"`
	Truncated bool          `json:"truncated,omitempty"`
}

Surface compares the outlines (signatures, types, imports, comments) of a file's two versions. An empty Lines with Truncated false means only function bodies changed.

func (*Surface) BodyOnly

func (s *Surface) BodyOnly() bool

BodyOnly reports whether the change left every declaration untouched.

type SurfaceLine

type SurfaceLine struct {
	Op      string `json:"op"`
	Text    string `json:"text"`
	Context string `json:"context,omitempty"`
}

SurfaceLine is one declaration-level line that differs between the base and head outlines. Context is the enclosing top-level declaration for indented lines such as struct fields or methods.

type Symbol

type Symbol struct {
	Name      string `json:"name"`
	Qualified string `json:"qualified,omitempty"`
	Kind      string `json:"kind"`
	Signature string `json:"signature,omitempty"`
	StartLine int    `json:"start_line"`
	EndLine   int    `json:"end_line"`
	Exported  bool   `json:"exported,omitempty"`
}

Symbol is a declaration in one version of a file, with one-based inclusive line numbers. Qualified names it within the file, such as "Store.load", and Signature is its text up to any body, such as "func (b *B) Load(x int) error".

type SymbolChange

type SymbolChange struct {
	Name     string `json:"name"`
	Kind     string `json:"kind"`
	Exported bool   `json:"exported,omitempty"`
	Change   string `json:"change"`
}

SymbolChange says how a head declaration was affected by the change.

type TextOptions

type TextOptions struct {
	// Markdown writes GitHub-flavoured Markdown instead of terminal text.
	Markdown bool
	// Declarations adds each file's declaration-level changes under it.
	Declarations bool
}

TextOptions control rendering.

Directories

Path Synopsis
cmd
review command
Package gitdiff reads a commit range from a local repository with the git binary and analyses it with the review package.
Package gitdiff reads a commit range from a local repository with the git binary and analyses it with the review package.
internal
gittest
Package gittest builds throwaway git repositories for tests.
Package gittest builds throwaway git repositories for tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL