an implementation of https://github.com/0xf0xx0/git-rivera in Pharo
  • Smalltalk 100%
Find a file
2026-09-21 21:01:47 +02:00
images add screenshot 2026-09-21 10:59:50 +02:00
src prepared to spec correctly (and using generator announcements to improve how it refreshes) 2026-09-21 10:59:18 +02:00
.gitignore updated to ignore backup files 2026-09-21 21:01:47 +02:00
.project first version 2026-08-25 20:18:58 +02:00
.properties first version 2026-08-25 20:18:58 +02:00
.smalltalk.ston first version 2026-08-25 20:18:58 +02:00
LICENSE first version 2026-08-25 20:18:58 +02:00
README.md add screenshot 2026-09-21 10:59:50 +02:00

GitRiver

Pulsar screenshot

GitRiver reads a Git history through the Pharo libgit2 bindings, assigns commits and edges to stable lanes, and renders the result as a Unicode river. It targets Pharo 14 and does not invoke the git executable.

The core deliberately has no GTK or Pulsar dependency. It provides both an eager pipeline for text rendering and a lazy, paginated model that a Spec presenter can consume directly.

Loading from a local checkout

Metacello new
    baseline: 'GitRiver';
    repository: 'tonel:///absolute/path/to/pharo-git-river/src';
    load: 'development'.

The baseline loads the Pharo14 branch of pharo-vcs/libgit2-pharo-bindings. The native libgit2 library must also be available to Pharo, as required by those bindings.

Usage

Render the history reachable from HEAD, with roots at the top:

GitRiver render: '/path/to/repository'.

Include every local branch, remote branch and tag:

GitRiver renderAll: '/path/to/repository'.

Start at any Git commit-ish. Branches, tags, commit hashes and revparse expressions are accepted:

GitRiver render: '/path/to/repository' startingAt: 'feature/login'.
GitRiver render: '/path/to/repository' startingAt: 'refs/remotes/origin/main'.
GitRiver render: '/path/to/repository' startingAt: '4f9a6e2'.
GitRiver render: '/path/to/repository' startingAt: 'HEAD~20'.

An explicit starting revision is the only traversal root. All branch and tag references are still collected and shown as decorations on reachable commits.

Use the individual stages directly:

history := (GitRiverHistoryReader on: '/path/to/repository')
    includeAllReferences;
    read.

layout := GitRiverLaneLayout new layout: history.

renderer := GitRiverTextRenderer new
    tipsFirst;
    hashLength: 10;
    messageLength: 60;
    yourself.

output := renderer render: layout.

Build a lazy model for a SpListPresenter or SpTablePresenter:

model := GitRiver
    historyListModelOn: '/path/to/repository'
    includeAllReferences: true
    pageSize: 500.

list model: model.

For a branch or commit selected in Pulsar, pass its Iceberg commit id. This is more reliable than reconstructing a local or remote reference name:

revision := lastBranchSelected branch commit id.
model := GitRiver
    historyListModelOn: self model location
    startingAt: revision
    pageSize: 500.

listPresenter model: model.

The first page is materialized immediately. Accessing rows near its end prefetches another page and announces the size change to Spec. Close the model when its presenter is no longer needed so a partially consumed native revwalk is released immediately:

model close.

The lazy stages can also be composed explicitly:

commits := (GitRiverHistoryReader on: '/path/to/repository') commitGenerator.
rows := GitRiverLaneLayout new rowGeneratorFrom: commits.
model := GitRiverHistoryListModel on: rows pageSize: 500.

GitRiverHistoryReader converts all native LGit* objects into ordinary Pharo snapshots before yielding them. Commits, the revwalk and the repository are released as the generator advances, finishes or is closed. GitRiverLaneLayout therefore has no FFI dependency and can be tested or reused independently.

Packages

  • GitRiver-Core: snapshots, libgit2 reader, lane layout and renderer.
  • GitRiver-Spec: a lightweight presenter for displaying one layout row as a graph column cell.
  • GitRiver-Tests: synthetic graph tests; no repository or git process is required.

Spec graph column

Use GitRiverLayoutRowPresenter as the cell presenter of the graph column in an easy column view:

listPresenter addColumn: (SpEasyColumnViewColumn new
    title: '';
    width: 100;
    cellPresenterClass: GitRiverLayoutRowPresenter;
    beNotExpandable;
    yourself).

Give every logical branch a stable color by configuring each graph cell with the same palette. Colors wrap around when the graph contains more simultaneous branches than entries in the palette:

palette := {
    Color red.
    Color blue.
    Color green.
    Color orange }.

graphColumn := SpEasyColumnViewColumn new
    title: '';
    width: 100;
    cellPresenterClass: GitRiverLayoutRowPresenter;
    whenCellPresenterInstantiatedDo: [ :presenter |
        presenter branchPalette: palette ];
    beNotExpandable;
    yourself.

listPresenter addColumn: graphColumn.

The layout stores only stable integer branch identities. Color objects stay in the Spec presenter, so the same rows can be rendered with another palette without recomputing repository history or lane layout.

Each list item must be a GitRiverLayoutRow. The presenter renders only that row's Unicode graph fragment; other columns can read commit data through layoutRow commit.

Scope

Implemented:

  • topological/date-ordered traversal;
  • HEAD-only and all-reference histories;
  • local branches, remote branches, tags and detached HEAD labels;
  • linear histories, multiple tips, forks, joins and octopus merges;
  • roots-first and tips-first Unicode rendering;
  • configurable hash and message lengths;
  • incremental revwalk, lane layout and paginated Spec list model.

Deferred to the Pulsar/GTK stage:

  • graphical drawing and interaction;
  • background loading;
  • worktree status badges;
  • custom themes and colored terminal output.

Design note

The lane allocator is an independent implementation based on Git's commit graph semantics. It does not translate or reuse the GPL-licensed layout code from git-rivera.