{"id":51510205,"url":"https://github.com/roblillack/gitj","last_synced_at":"2026-07-08T05:01:26.523Z","repository":{"id":363189395,"uuid":"789031452","full_name":"roblillack/gitj","owner":"roblillack","description":"Git Journey: Classic-looking, cross-platform GUI git client","archived":false,"fork":false,"pushed_at":"2026-06-07T20:55:16.000Z","size":7417,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-07T21:21:47.223Z","etag":null,"topics":["git","gui","macos","rust","saudade","wayland","windows","x11"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/roblillack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-04-19T15:13:13.000Z","updated_at":"2026-06-07T20:19:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/roblillack/gitj","commit_stats":null,"previous_names":["roblillack/gitj"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/roblillack/gitj","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roblillack%2Fgitj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roblillack%2Fgitj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roblillack%2Fgitj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roblillack%2Fgitj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/roblillack","download_url":"https://codeload.github.com/roblillack/gitj/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roblillack%2Fgitj/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35252324,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["git","gui","macos","rust","saudade","wayland","windows","x11"],"created_at":"2026-07-08T05:01:25.838Z","updated_at":"2026-07-08T05:01:26.507Z","avatar_url":"https://github.com/roblillack.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Git Journey\n\n[![CI](https://github.com/roblillack/gitj/actions/workflows/ci.yml/badge.svg)](https://github.com/roblillack/gitj/actions/workflows/ci.yml)\n[![crates.io](https://img.shields.io/crates/v/gitj.svg)](https://crates.io/crates/gitj)\n[![license](https://img.shields.io/crates/l/gitj.svg)](LICENSE)\n\nA gitk-style repository browser and commit helper built on the\n[Saudade](https://github.com/roblillack/saudade) toolkit.\n\n```\ngitj            # browse the repository containing the current directory\ngitj /path/repo # browse the repository at (or above) a given path\ngitj -c         # open straight onto the commit (staging) screen\ngitj -r         # open the branch-review screen on the checked-out branch\ngitj -r feat/x  # open the branch-review screen with feat/x selected\ngitj --version  # print the version and exit\ngitj --help     # print usage and exit\n```\n\nGit Journey has two screens — the gitk-style **browse** (history) screen and the\n`git gui`-style **commit** (staging) screen. Switch between them from the\n**View** menu, by double-clicking a working-tree entry in the log, or\nautomatically: committing drops you back to the log.\n\n## Screenshots\n\nBrowse screen — gitk-style history with the DAG graph, ref badges and a\n`git show`-style detail/diff pane:\n\n![The browse screen](docs/screenshot-browse.png)\n\nCommit screen — `git gui`-style staging with unstaged/staged lists, a per-file\ndiff and the message editor:\n\n![The commit screen](docs/screenshot-commit.png)\n\n\u003c!--\nThese images are rendered by [`examples/screenshots.rs`](examples/screenshots.rs),\nwhich drives the real UI against the in-memory `FixtureBackend` through saudade's\noffscreen `MockBackend`, then wraps each window in Canoe-style chrome — title\nbar, frame and drop shadow — with `render_framed` and captures it at 2× for\ncrisp hi-DPI output. Regenerate them with `cargo run --example screenshots`.\n--\u003e\n\n\n### Installation\n\n```sh\ncargo install gitj\n```\n\n### Browse\n\n* **Commit history** with a colored DAG **graph** column, branch / tag /\n  HEAD **ref badges**, and author + date columns.\n* **`git show`-style detail**: selecting a commit shows its SHA, refs,\n  author, date, message and full diff; selecting a file narrows the diff to\n  that file.\n* **Diff view** with the usual coloring — green additions, red deletions,\n  blue hunk headers, gray file headers.\n* **Graphical image diff**: selecting a changed image (PNG, JPEG, GIF, WebP,\n  BMP, TIFF, …) shows the two versions visually instead of a \"binary files\n  differ\" line. Compare them side by side (**2-up**), with a **swipe** split, an\n  **onion-skin** cross-fade, or a per-pixel **difference** heatmap — switched\n  from the button row or the **View** menu (**Switch Mode**, Ctrl+M), with a\n  slider driving the swipe / onion position. **Before** / **After Image**\n  (Ctrl+Left / Ctrl+Right) show just the old / new side full size, and **Next** /\n  **Previous Image** (Ctrl+N / Ctrl+P) step between the image files in the active\n  list. Works in both the browse and commit diff panes.\n* **Working-tree entries**: when there are local changes, the log leads with\n  \"Uncommitted changes\" / \"Staged changes\" rows (connected into the graph at\n  `HEAD`). Selecting one previews its files and diff; **double-clicking** it\n  opens the commit screen.\n* **Search / filter** the history live by message, author, ref or SHA.\n\n### Commit\n\n* **Unstaged** and **Staged** file lists (à la `git gui`). Double-click a\n  file to stage / unstage it, or use the **Stage** / **Unstage** buttons.\n* The **diff pane** shows the selected file's change — working-tree-vs-index\n  for unstaged files, index-vs-`HEAD` for staged ones.\n* **Revert / discard** an unstaged change (`git gui`'s **Revert Changes**,\n  **Ctrl+J**): a tracked file is reset to its index copy, while an *untracked*\n  file — having nothing to revert to — is offered up for deletion instead.\n  Either way a confirm dialog guards the irreversible discard.\n* A multi-line **message editor** and a **Commit** button.\n* **Amend last commit**: ticking the box pre-fills the editor with `HEAD`'s\n  message *and* re-bases the staging view on `HEAD`'s parent, so the changes\n  already in the last commit show up as staged. Unstage any of them to drop\n  them from the amended commit; committing then rewrites `HEAD` rather than\n  adding a new commit.\n* **Rescan** re-reads the working tree. Committing returns to the log view,\n  which now shows the new commit.\n* **Keyboard shortcuts** mirror `git gui` (and are shown next to each menu\n  item): **Ctrl+B** / **Ctrl+C** switch between the **Browse History** and\n  **Commit Changes** screens — the active one is checkmarked in the **View**\n  menu — **Ctrl+Enter** commits, **Ctrl+T** stages the selected file, **Ctrl+I**\n  stages everything, **Ctrl+U** unstages the selected file, **Ctrl+J** reverts\n  (or deletes) the selected unstaged file, **Ctrl+S** appends a `Signed-off-by`\n  trailer, and **Ctrl+R** rescans. The\n  message editor takes the usual **Ctrl+C / X / V / A**: Ctrl+C only switches\n  screens from the browse view, so it stays available as copy while you edit the\n  commit message. **Ctrl+Q** quits from either screen.\n\n### Throughout\n\n* **Menu bar** (File ▸ Reload / Exit, View ▸ switch screen, Help ▸ About) with\n  Alt-accelerators and a modal About / error dialog.\n* Keyboard navigation: Tab cycles the panes, arrows/PageUp/Down drive the\n  focused list or scroll the diff.\n\n## Architecture\n\nThe UI never touches `git2` directly — it goes through a small backend\nabstraction, which keeps everything testable without a live repository.\n\n| Module | Contents |\n|--------|----------|\n| `backend` | `RepoBackend` trait + data types (`CommitInfo`, `FileChange`, `Diff`/`DiffLine`, `BlobPair`, `RefLabel`, `WorkingStatus`). Browse reads history/diffs; commit mode adds working-tree status, per-file diffs, `stage`/`unstage`, `revert`/`delete_untracked`, `commit` (with amend); image diffs read the two sides' raw blobs (`commit_file_blobs`/`working_file_blobs`). Implementations: `Git2Backend` (live, libgit2) and `FixtureBackend` (deterministic, in-memory, with a simulated working tree). |\n| `imagediff` | Decodes image blobs (via the `image` crate) and composes the two sides into a comparison canvas for the 2-up / swipe / onion-skin / difference modes — the toolkit-independent half of the graphical diff. |\n| `widgets` | git-specific widgets — `CommitList` (graph + badges + columns), `DiffView` (colored diff), `ImageDiffView` (graphical image diff) wrapped by `DiffPane` (shows whichever the file calls for), `SearchBar`, `Heading`, `graph` (DAG lane assignment); `Shell`, a generic flat-focus container, plus a `layout` module giving the browse and commit screens their rectangles; generic `Shared\u003cW\u003e` adapter. |\n| `ui` | `GitClient`, the top-level widget. It owns both screens (a `Shell` each), switches between them, and — since saudade widgets are callback-free — polls selections and a small command queue after each event to rebuild dependent panes. |\n\n## Testing\n\n* **Pixel snapshots** (`tests/snapshots.rs`) render the real UI through\n  saudade's offscreen `MockBackend` at 1.0× against the\n  in-memory `FixtureBackend` and bundled DejaVu fonts, comparing PNG bytes\n  with `insta`. Regenerate with `INSTA_UPDATE=always cargo test --test\n  snapshots`, then review with `cargo insta review`.\n* **`tests/git2_backend.rs`** builds a throwaway repository with fixed\n  signatures/timestamps and reads it back through `Git2Backend`, so the live\n  backend is covered deterministically.\n* **`tests/image_blobs.rs`** commits and edits a PNG in a throwaway repository\n  and asserts `Git2Backend` returns the exact blob bytes for each side of a\n  commit and working-tree image change.\n* **`tests/commit_backend.rs`** exercises commit mode end to end: it stages,\n  unstages, reverts, commits and amends in a throwaway repository (and against\n  the fixture), asserting the working-tree status at each step.\n* **`tests/shortcuts.rs`** drives a real `GitClient` through synthetic key\n  events (Ctrl+T / I / J / Enter / Q) and asserts the resulting working-tree\n  and commit state — the runtime path minus the windowing.\n* **Unit tests** cover the graph lane algorithm and date formatting.\n\n```\ncargo test          # everything\ncargo build         # the binary\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froblillack%2Fgitj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froblillack%2Fgitj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froblillack%2Fgitj/lists"}