Skip to content

Commit c198989

Browse files
committed
Dress the repo for company: readme, licence, ci
A picture of the viewer playing itself, a licence file to match the package, cross-platform browser opening, and one copy of three.js instead of two.
1 parent fdd2dcb commit c198989

10 files changed

Lines changed: 170 additions & 9 deletions

File tree

‎.github/workflows/check.yml‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: check
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
check:
10+
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
node: ["18", "20", "22"]
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: actions/setup-node@v4
17+
with:
18+
node-version: ${{ matrix.node }}
19+
- run: make check

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules/
22
*.log
3+
.claude/

‎LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Venkatesh Dayanand
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎README.md‎

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# gitdiff3d
2+
3+
A git diff is a pile of text. Somebody typed that text, one key at a time, in
4+
some order that made sense to them. `gitdiff3d` plays it back: your diff becomes
5+
a 3d scene where every changed line is a plate floating in space, and a caret
6+
walks through them typing the additions in and erasing the deletions.
7+
8+
![the viewer playing a hunk](docs/screenshot.png)
9+
10+
## Install
11+
12+
```
13+
git clone https://github.com/vdayanand/gitdiff3d
14+
cd gitdiff3d
15+
make install # symlinks bin/gitdiff3d.js into ~/bin
16+
```
17+
18+
Or run it straight out of the checkout with `node bin/gitdiff3d.js`.
19+
20+
Node 18 or newer. No dependencies: three.js is vendored, everything else is the
21+
standard library and the `git` already on your path.
22+
23+
## Use
24+
25+
```
26+
gitdiff3d working tree vs HEAD
27+
gitdiff3d --staged the index
28+
gitdiff3d HEAD~3..HEAD a commit range
29+
gitdiff3d main HEAD two revisions
30+
gitdiff3d -- src/ limit to a path
31+
```
32+
33+
It builds the scene, serves it on a random local port, and opens your browser.
34+
35+
```
36+
--port <n> listen on port n (default: random)
37+
--max-hunks <n> keep only the first n hunks (default: all)
38+
--order <how> story or git (default: story)
39+
--context <n> lines of unchanged code around each hunk (default: 8)
40+
--json print the scene model, do not serve
41+
--no-open serve without opening a browser
42+
```
43+
44+
## Story order
45+
46+
Git prints hunks in path order, which is alphabetical, which is nobody's
47+
thinking. The default `--order story` reorders them the way the change was
48+
probably made: a hunk that introduces a name comes before the hunks that call
49+
that name, and failing that, schema before models before logic before callers
50+
before tests before prose. It is a topological sort over "who defines what",
51+
with the file's role as the tie breaker. `--order git` turns it off.
52+
53+
## Overlay
54+
55+
Press `l` and the old version of the hunk sits behind the new one, both in
56+
frame, separated in depth rather than side by side. This is the one thing a flat
57+
diff cannot do.
58+
59+
![the old version behind the new one](docs/overlay.png)
60+
61+
## Keys
62+
63+
```
64+
space pause , . a keystroke back or forward
65+
< > a whole line b r rewind this hunk
66+
arrows previous or next hunk a autoplay through everything
67+
j / k scroll wheel, ctrl-wheel zoom, shift-wheel sideways
68+
drag orbit 0 refit the camera and follow the caret
69+
c more or less context (refetched from git)
70+
l overlay g / G fade the front or back plane
71+
t / T next or previous theme f fullscreen
72+
x / X faster or slower m keystroke sound v speak the file and symbol
73+
? show or hide the shortcuts
74+
```
75+
76+
Twelve editor themes, dark and light: tokyo night, dracula, one dark, nord,
77+
gruvbox, solarized, github light, one light, gruvbox light, everforest light,
78+
mint. A theme names only the colours an editor names; the papers, rims and
79+
chrome are mixed from those, so nothing needs a table of shades.
80+
81+
## How it works
82+
83+
```
84+
git diff -U8 -> parse-diff -> structure -> layout -> order -> viewer
85+
hunks and which symbol scene story three.js,
86+
lines each hunk is model sort one plate
87+
inside per line
88+
```
89+
90+
The server holds the scene model and re-runs the diff only when you ask for a
91+
different context width. Everything the browser needs is one JSON document plus
92+
one HTML file, so the viewer is dumb on purpose: it draws what it is given.
93+
94+
Source structure is understood by regex, not by a real parser. It knows c-like
95+
languages (js, ts, go, rust, java, kotlin, swift, c#, c, c++, php, scala, dart),
96+
python, julia and ruby well enough to name the function a hunk lands in and to
97+
tell a comment-only change from a real one. Other languages still render, they
98+
just get no symbol names and no story order.
99+
100+
Binary files are skipped. Very long lines are clipped to 200 characters.
101+
102+
## Licence
103+
104+
MIT, see [LICENSE](LICENSE). Bundles [three.js](https://threejs.org) r128,
105+
also MIT.

‎bin/gitdiff3d.js‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ function readArgs(argv) {
4141
return { flags, revisions };
4242
}
4343

44+
// Whatever this platform calls "hand this url to the browser". A missing
45+
// opener is not worth failing over; the url is already printed.
46+
const openerFor = (platform) =>
47+
platform === "darwin" ? ["open", []]
48+
: platform === "win32" ? ["cmd", ["/c", "start", ""]]
49+
: ["xdg-open", []];
50+
51+
const openBrowser = (url) => {
52+
const [command, args] = openerFor(process.platform);
53+
execFile(command, [...args, url], () => {});
54+
};
55+
4456
const describe = (revisions, cwd) =>
4557
revisions.length ? revisions.join(" ") : "working tree vs " + headLabel(cwd);
4658

@@ -88,7 +100,7 @@ async function main() {
88100
scene.totals.additions + " -" + scene.totals.deletions + " -> " + url);
89101
console.log("keys: space pause, , . keystroke, < > line, arrows hunk, l overlay, c context,\n" +
90102
" j/k scroll, x/X speed, m sound, v voice, f full, a autoplay, ? help");
91-
if (flags.open) execFile("open", [url]);
103+
if (flags.open) openBrowser(url);
92104
}
93105

94106
main().catch((err) => {

‎docs/overlay.png‎

587 KB
Loading

‎docs/screenshot.png‎

460 KB
Loading

‎mockups/gitdiff3d-mockups.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
</div>
5454
<div id="hint">drag to orbit &middot; wheel to zoom &middot; keys 1-5</div>
5555

56-
<script src="three.min.js"></script>
56+
<script src="../src/vendor/three.min.js"></script>
5757
<script>
5858
/* ---------- fake repository ---------- */
5959
const rand = (() => { let s = 20260903; return () => (s = (s * 1103515245 + 12345) % 2147483648) / 2147483648; })();

‎mockups/three.min.js‎

Lines changed: 0 additions & 1 deletion
This file was deleted.

‎package.json‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
{
22
"name": "gitdiff3d",
33
"version": "0.1.0",
4-
"description": "git diff rendered as a 3d scene",
5-
"bin": { "gitdiff3d": "./bin/gitdiff3d.js" },
6-
"main": "src/index.js",
4+
"description": "git diff rendered as a 3d scene, played back hunk by hunk",
5+
"keywords": ["git", "diff", "3d", "three", "code-review", "visualization", "cli"],
6+
"homepage": "https://github.com/vdayanand/gitdiff3d#readme",
7+
"bugs": "https://github.com/vdayanand/gitdiff3d/issues",
8+
"repository": { "type": "git", "url": "git+https://github.com/vdayanand/gitdiff3d.git" },
9+
"author": "Venkatesh Dayanand",
10+
"license": "MIT",
711
"type": "commonjs",
8-
"engines": { "node": ">=18" },
9-
"files": ["bin", "src"],
10-
"license": "MIT"
12+
"bin": { "gitdiff3d": "./bin/gitdiff3d.js" },
13+
"files": ["bin", "src", "README.md", "LICENSE"],
14+
"engines": { "node": ">=18" }
1115
}

0 commit comments

Comments
 (0)