This commit is contained in:
2026-01-21 11:27:51 -05:00
commit f70af9b185
50 changed files with 17166 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
.cpcache/
.nrepl-port
target/
*.jar
# Generated test outputs
*.gif
*.png
test/e2e/output/
+179
View File
@@ -0,0 +1,179 @@
# Claude Code Instructions for lazygitclj
## Clojure Development
- Use Clojure skills (nREPL evaluation) when editing code to verify changes compile and work correctly
- Evaluate code in the REPL to test functions before committing changes
- Run `bb start` to launch the application, `bb test` for unit tests
## Local TUI Library
- The TUI library is at `../clojure-tui/` (local dependency in bb.edn)
- You have access to edit the local TUI library in conjunction with this repo
- Use this access to debug issues, support new features, and simplify code
- Look for opportunities to create better abstraction primitives for TUI layout
## Testing with VHS
- Use VHS to test all changes and verify behavior before finishing any task
- Run `bb test:e2e` to run VHS tape tests
- Run VHS tape files to capture terminal recordings and validate UI behavior
- Do not consider a task complete until changes have been verified with VHS
## Understanding Expected Behavior
- Reference the lazygit repository and documentation to understand expected behaviors
- Use VHS locally with lazygit to compare and verify that lazygitclj matches expected interaction patterns
- When implementing features, check how lazygit handles the same functionality
- See `PRD.md` for detailed lazygit behavior documentation
---
## Project Overview
lazygitclj is a lazygit-inspired TUI for Git written in Clojure, targeting Babashka for fast startup. It follows the Elm Architecture pattern (Model-Update-View).
## Project Structure
```
src/lazygitclj/
├── core.clj # Main TUI app: views, update logic, keybindings (883 lines)
├── git.clj # Git operations wrapper via shell commands (478 lines)
└── debug_tui.clj # Layout debugging utility
test/
├── lazygitclj/
│ ├── core_test.clj # Model/update function tests
│ └── git_test.clj # Git operations tests (uses temp repo fixture)
└── run-tests.clj # Test runner
```
## Build Commands
| Command | Purpose |
|---------|---------|
| `bb start` | Run lazygitclj TUI |
| `bb debug` | Debug TUI layout issues |
| `bb test` | Run unit tests |
| `bb test:e2e` | Run VHS tape tests |
## Architecture: Elm Pattern
```clojure
(tui/run {:init (initial-model)
:update update-model
:view view})
```
- **Model**: Application state as a map
- **Update**: Pure functions returning `[new-model command]` tuples
- **View**: Pure functions rendering hiccup-style VDom
## Model Structure
```clojure
{:panel :files ;; Current panel: :files, :branches, :commits, :stash
:cursor 0 ;; Cursor position in current panel
:message nil ;; Transient message display
:input-mode nil ;; :commit, :new-branch, :rename-branch, etc.
:input-buffer "" ;; Text input during modal entry
:menu-mode nil ;; :stash-options, :reset-options, :help
:commits-tab :commits ;; :commits or :reflog
:branches-tab :local ;; :local, :remotes, or :tags
:branch "main" ;; Current branch
:sha "abc1234" ;; HEAD SHA
:ahead-behind [0 0] ;; [ahead, behind] upstream
:staged [...] ;; Staged files
:unstaged [...] ;; Unstaged/untracked files
:commits [...] ;; Recent commits
:branches [...] ;; Local branches
:remote-branches [...] ;; Remote branches
:tags [...] ;; Tags
:stashes [...] ;; Stash entries
:reflog [...] ;; Reflog for undo/redo
:diff nil} ;; Currently displayed diff
```
## Key Functions in core.clj
| Function | Purpose |
|----------|---------|
| `initial-model` | Create initial application state |
| `load-git-data` | Load all git state via git.clj |
| `refresh` | Reload git data and update diff |
| `update-model` | Main dispatcher for keyboard input |
| `update-files` | Handle files panel (stage, unstage, commit) |
| `update-commits` | Handle commits panel (checkout, cherry-pick, revert) |
| `update-branches` | Handle branches panel (checkout, create, delete, merge) |
| `update-stash` | Handle stash panel (apply, pop, drop) |
| `file-items` | Combine staged/unstaged files with type metadata |
| `current-items` | Get items for current panel |
| `view` | Render UI based on state and dimensions |
## Keybindings
**Global**: `q` quit, `?` help, `r` refresh, `h/l` panel nav, `j/k` cursor, `z/Z` undo/redo, `p/P` pull/push, `D` reset menu
**Files**: `space` stage/unstage, `a` stage all, `c` commit, `d` discard, `s/S` stash
**Commits**: `space` checkout, `g` reset to, `C` cherry-pick, `t` revert, `r` reword, `[/]` tabs
**Branches**: `enter` checkout, `n` new, `d` delete, `R` rename, `M` merge, `f` fast-forward, `[/]` tabs
**Stash**: `space` apply, `g` pop, `d` drop, `n` branch from stash
## Git Operations (git.clj)
All git operations wrap `babashka.process/shell` and return `nil` on error.
**Categories**:
- Status: `current-branch`, `head-sha`, `ahead-behind`
- Files: `status-files`, `staged-files`, `unstaged-files`
- Staging: `stage-file`, `unstage-file`, `stage-all`
- Diff: `diff-file`, `diff-staged`, `show-commit`, `diff-branch`
- Actions: `commit`, `discard-file`, `pull`, `push`
- Stash: `stash-list`, `stash-push`, `stash-pop`, `stash-apply`, `stash-drop`
- Reset: `reset-soft`, `reset-mixed`, `reset-hard`
- Branches: `create-branch`, `checkout-branch`, `delete-branch`, `merge-branch`
- Tags: `list-tags`, `create-tag`, `delete-tag`
## TUI Library Primitives
```clojure
;; Containers
[:row {:widths [30 :flex] :gap 1} left right]
[:col {:heights [3 :flex :flex 4]} top mid1 mid2 bottom]
[:box {:border :double :title "Title"} content]
;; Text with styling
[:text {:fg :green :bold true} "content"]
```
- `:flex` keyword items share remaining space equally
- Borders: `:single`, `:double`, `:rounded`
- Colors: `:red`, `:green`, `:yellow`, `:blue`, `:magenta`, `:cyan`, `:white`
## Responsive Layout
- **Narrow (< 70 cols)**: Single-column stacked layout
- **Wide (>= 70 cols)**: Two-column (left panels 30 cols, right diff flex)
## File Status Format
Files parsed from `git status --porcelain`:
```clojure
{:index \M :worktree \space :path "file.txt"}
```
## Diff Coloring
- `+` lines: green (additions)
- `-` lines: red (deletions)
- `@@` lines: cyan (hunk headers)
- `diff`/`commit` lines: yellow (headers)
## Conventions
- Predicates end with `?` (e.g., `can-fast-forward?`)
- Private functions prefixed with `-` (e.g., `-sh`, `-parse-status-line`)
- Update functions return `[new-model command]` tuples
- Messages are transient (cleared on next refresh)
+851
View File
@@ -0,0 +1,851 @@
# lazygit - Product Requirements Document
## Overview
**lazygit** is a simple terminal UI for git commands, written in Go. It provides an intuitive, keyboard-driven interface for managing git repositories without memorizing complex git commands.
- **Version**: 0.58.1
- **Author**: Jesse Duffield
- **Repository**: https://github.com/jesseduffield/lazygit
- **License**: MIT
## Table of Contents
1. [Core Concepts](#core-concepts)
2. [User Interface](#user-interface)
3. [Panel System](#panel-system)
4. [Keybindings](#keybindings)
5. [Git Operations](#git-operations)
6. [Configuration](#configuration)
7. [CLI Interface](#cli-interface)
8. [Automation with VHS](#automation-with-vhs)
---
## Core Concepts
### Design Philosophy
- **Keyboard-first**: All operations accessible via keyboard shortcuts
- **Context-aware**: Keybindings change based on focused panel and selected item
- **Visual feedback**: Real-time diff views, color-coded file states
- **Non-destructive**: Confirmation prompts for dangerous operations
- **Discoverable**: Built-in help system (`?`) shows available actions
### File States
| Indicator | Meaning |
|-----------|---------|
| `M` | Modified (tracked file changed) |
| `A` | Added (new file staged) |
| `D` | Deleted |
| `R` | Renamed |
| `C` | Copied |
| `??` | Untracked (new file not staged) |
| `UU` | Unmerged (conflict) |
---
## User Interface
### Layout Overview
```
+--[1]-Status---------+--[0]-Main View (Diff/Log/Patch)--------+
| repo-name -> branch | |
+--[2]-Files----------+ |
| Files/Worktrees/ | |
| Submodules | |
+--[3]-Branches-------+ |
| Local/Remotes/Tags | |
+--[4]-Commits--------+-----------------------------------------+
| Commits/Reflog |--Command log---------------------------+|
+--[5]-Stash----------+ ||
| Stash entries | ||
+---------------------+----------------------------------------++
| Status bar: Quick actions | Keybindings: ? | Version 0.58.1|
+------------------------------------------------------------------+
```
### Panel Numbers
| # | Panel | Description |
|---|-------|-------------|
| 0 | Main View | Displays diffs, logs, patches, commit details |
| 1 | Status | Repository name and current branch |
| 2 | Files | Working tree changes, worktrees, submodules |
| 3 | Branches | Local branches, remotes, tags |
| 4 | Commits | Commit history, reflog |
| 5 | Stash | Stash entries |
### Navigation Between Panels
| Key | Action |
|-----|--------|
| `1-5` | Jump directly to panel by number |
| `0` | Focus main view |
| `h/l` or `Left/Right` | Move between panels |
| `Tab` | Toggle between side panels and main view |
| `[` / `]` | Switch tabs within a panel |
### Screen Modes
| Key | Mode | Description |
|-----|------|-------------|
| `+` | Next screen mode | Cycles: normal -> half -> full |
| `_` | Previous screen mode | Cycles backwards |
---
## Panel System
### Panel 1: Status
Displays the repository name and current branch.
**Actions:**
| Key | Action |
|-----|--------|
| `Enter` | Recent repositories |
| `a` | All branches log graph |
| `u` | Check for updates |
### Panel 2: Files / Worktrees / Submodules
**Tabs:** Files | Worktrees | Submodules
#### Files Tab
**File Operations:**
| Key | Action |
|-----|--------|
| `Space` | Stage/unstage file |
| `a` | Stage/unstage all files |
| `Enter` | Enter staging view (line-by-line staging) |
| `d` | Discard changes |
| `D` | Reset options menu |
| `e` | Edit file |
| `o` | Open file |
| `i` | Ignore file (.gitignore) |
| `r` | Refresh files |
| `` ` `` | Toggle file tree view |
| `-` | Collapse all directories |
| `=` | Expand all directories |
**Commit Operations:**
| Key | Action |
|-----|--------|
| `c` | Commit staged changes |
| `C` | Commit with editor |
| `w` | Commit without pre-commit hook |
| `A` | Amend last commit |
| `Ctrl+f` | Find base commit for fixup |
**Stash Operations:**
| Key | Action |
|-----|--------|
| `s` | Stash all changes |
| `S` | Stash options menu |
**Stash Options Menu:**
- `a` - Stash all changes
- `i` - Stash all changes and keep index
- `U` - Stash all changes including untracked files
- `s` - Stash staged changes
- `u` - Stash unstaged changes
**Reset Options Menu (`D`):**
- `u` - Discard unstaged changes (`git checkout -- .`)
- `d` - Discard untracked files (`git clean -fd`)
- `S` - Discard staged changes (`stash staged and drop stash`)
- `s` - Soft reset (`git reset --soft HEAD`)
- `m` - Mixed reset (`git reset --mixed HEAD`)
- `h` - Hard reset (`git reset --hard HEAD`)
#### Worktrees Tab
| Key | Action |
|-----|--------|
| `n` | New worktree |
| `Space` | Switch to worktree |
| `d` | Remove worktree |
| `w` | View worktree options |
#### Submodules Tab
| Key | Action |
|-----|--------|
| `i` | Initialize submodule |
| `u` | Update submodule |
| `b` | Bulk menu |
| `Enter` | Enter submodule |
### Panel 3: Local Branches / Remotes / Tags
**Tabs:** Local branches | Remotes | Tags
#### Local Branches Tab
| Key | Action |
|-----|--------|
| `Space` | Checkout branch |
| `n` | New branch |
| `d` | Delete branch |
| `r` | Rebase current branch onto selected |
| `R` | Rename branch |
| `M` | Merge selected into current branch |
| `f` | Fast-forward branch |
| `F` | Force checkout |
| `-` | Checkout previous branch |
| `u` | Set upstream |
| `i` | View git-flow options |
| `o` | Create pull request |
| `O` | View pull request options |
| `Ctrl+y` | Copy pull request URL |
| `T` | Create tag |
| `s` | Sort order |
**Rebase Menu (`r`):**
- `s` - Simple rebase onto selected branch
- `i` - Interactive rebase onto selected branch
- `b` - Rebase onto base branch
#### Remotes Tab
| Key | Action |
|-----|--------|
| `Enter` | View branches |
| `n` | New remote |
| `d` | Remove remote |
| `e` | Edit remote |
| `f` | Fetch from remote |
| `F` | Add fork remote |
#### Tags Tab
| Key | Action |
|-----|--------|
| `Ctrl+o` | Copy tag to clipboard |
| `Space` | Checkout tag |
| `n` | New tag |
| `d` | Delete tag |
| `P` | Push tag |
| `g` | Reset to tag |
| `Enter` | View commits |
| `w` | View worktree options |
### Panel 4: Commits / Reflog
**Tabs:** Commits | Reflog
#### Commits Tab
**Navigation & Selection:**
| Key | Action |
|-----|--------|
| `j/k` | Move up/down |
| `Space` | Checkout commit |
| `Enter` | View commit files |
| `v` | Toggle range select |
| `*` | Select commits of current branch |
**Commit Manipulation:**
| Key | Action |
|-----|--------|
| `r` | Reword commit message |
| `R` | Reword with editor |
| `e` | Edit commit (start interactive rebase) |
| `d` | Drop commit |
| `s` | Squash down |
| `S` | Squash all above commits |
| `f` | Mark as fixup |
| `F` | Create fixup commit |
| `p` | Pick commit |
| `Ctrl+j` | Move commit down |
| `Ctrl+k` | Move commit up |
| `i` | Start interactive rebase |
**Advanced Operations:**
| Key | Action |
|-----|--------|
| `A` | Amend commit with staged changes |
| `a` | Reset commit author |
| `t` | Revert commit |
| `T` | Tag commit |
| `C` | Cherry-pick copy |
| `V` | Paste (cherry-pick) commits |
| `Ctrl+R` | Reset cherry-pick selection |
| `B` | Mark as base for rebase |
| `b` | View bisect options |
| `g` | View reset options |
| `y` | Copy commit attribute to clipboard |
| `o` | Open commit in browser |
| `Ctrl+l` | Open log menu |
| `n` | Create new branch off commit |
**Bisect Menu (`b`):**
- `b` - Mark commit as bad (start bisect)
- `g` - Mark commit as good (start bisect)
- `t` - Choose bisect terms
- Cancel
#### Reflog Tab
Shows the reference log for recovering lost commits.
| Key | Action |
|-----|--------|
| `Space` | Checkout reflog entry |
| `g` | View reset options |
| `C` | Cherry-pick copy |
| `V` | Paste commits |
### Panel 5: Stash
| Key | Action |
|-----|--------|
| `Space` | Apply stash |
| `g` | Pop stash |
| `d` | Drop stash |
| `n` | New branch from stash |
| `r` | Rename stash |
| `Enter` | View stash files |
---
## Keybindings
### Global Keybindings
**Navigation:**
| Key | Action |
|-----|--------|
| `j/k` or `Up/Down` | Move up/down in list |
| `h/l` or `Left/Right` | Move between panels |
| `<` / `>` | Go to top/bottom of list |
| `Home` / `End` | Go to top/bottom of list |
| `,` / `.` | Page up/down |
| `PgUp` / `PgDown` | Scroll main window |
| `J/K` or `Ctrl+u/Ctrl+d` | Scroll main window |
| `H` / `L` | Scroll left/right |
**Actions:**
| Key | Action |
|-----|--------|
| `?` | Show keybindings help |
| `Enter` | Confirm / Go into |
| `Esc` | Return / Cancel |
| `Space` | Primary action (context-dependent) |
| `/` | Start search |
| `n` / `N` | Next/previous search match |
| `q` | Quit |
| `Q` | Quit without changing directory |
| `Ctrl+c` | Quit (alternative) |
| `Ctrl+z` | Suspend application |
**Git Operations:**
| Key | Action |
|-----|--------|
| `P` | Push |
| `p` | Pull |
| `f` | Fetch (in files panel) |
| `R` | Refresh |
| `z` | Undo (reflog) |
| `Z` | Redo (reflog) |
| `m` | View merge/rebase options |
**View Options:**
| Key | Action |
|-----|--------|
| `@` | View command log options |
| `+` / `_` | Next/previous screen mode |
| `\|` | Cycle pagers |
| `W` or `Ctrl+e` | Diffing menu |
| `Ctrl+w` | Toggle whitespace in diff |
| `}` / `{` | Increase/decrease diff context |
| `)` / `(` | Increase/decrease rename similarity threshold |
| `Ctrl+t` | Open external diff tool |
| `Ctrl+s` | Filtering menu |
**Other:**
| Key | Action |
|-----|--------|
| `:` | Execute shell command |
| `Ctrl+r` | Switch to recent repo |
| `Ctrl+o` | Copy to clipboard |
| `Ctrl+p` | View custom patch options |
### Staging View Keybindings
When viewing a file's diff (entered via `Enter` on a file):
| Key | Action |
|-----|--------|
| `Space` | Stage/unstage line |
| `a` | Toggle select hunk |
| `A` | Stage/unstage all changes |
| `v` | Toggle range select |
| `j/k` | Move up/down |
| `J/K` | Scroll diff up/down |
| `E` | Edit hunk in editor |
| `e` | Edit file |
| `o` | Open file |
| `Esc` | Return to files panel |
| `Tab` | Switch to staged/unstaged changes |
| `b` | Pick both hunks (merge conflicts) |
---
## Git Operations
### Committing
1. **Stage changes**: `Space` on files or `a` to stage all
2. **Commit**: `c` to commit with inline message
3. **Commit with editor**: `C` to open editor for commit message
4. **Amend**: `A` to amend the last commit with staged changes
5. **Commit without hooks**: `w` to skip pre-commit hooks
### Branching
1. **Create branch**: `n` in branches panel
2. **Checkout**: `Space` on branch
3. **Delete**: `d` on branch
4. **Rename**: `R` on branch
5. **Checkout previous**: `-` in branches panel
### Merging
1. Navigate to branches panel (`3`)
2. Select the branch to merge
3. Press `M` to merge into current branch
**During merge conflicts:**
- Files with `UU` status indicate conflicts
- Enter file to resolve conflicts line-by-line
- Use `b` to pick both sides
- Stage resolved files with `Space`
- Continue merge with `m` -> continue option
### Rebasing
**Simple rebase:**
1. Go to branches panel
2. Select target branch
3. Press `r` to open rebase menu
4. Choose rebase type
**Interactive rebase:**
1. Go to commits panel
2. Navigate to the commit before where you want to start
3. Press `i` to start interactive rebase
4. Use `p` (pick), `s` (squash), `f` (fixup), `d` (drop), `e` (edit)
5. Reorder commits with `Ctrl+j/k`
6. Press `m` to view rebase options and continue/abort
### Cherry-picking
1. Navigate to the commit to cherry-pick
2. Press `C` to copy the commit
3. Switch to the target branch
4. Press `V` to paste (cherry-pick)
5. Use `Ctrl+R` to reset cherry-pick selection
### Stashing
**Quick stash:**
- `s` in files panel stashes all changes
**Stash options (`S`):**
- Stash all changes
- Stash including untracked
- Stash only staged
- Stash only unstaged
- Keep index
**Using stashes:**
- `Space` - Apply stash (keep stash)
- `g` - Pop stash (apply and remove)
- `d` - Drop stash
### Bisecting
1. Go to commits panel
2. Press `b` to open bisect menu
3. Mark a known bad commit with `b`
4. Mark a known good commit with `g`
5. Test each commit lazygit checks out
6. Mark as good/bad until bug is found
7. Reset bisect when done
### Working with Remotes
**Push:**
- `P` globally pushes current branch
- Force push available in push options menu
**Pull:**
- `p` globally pulls current branch
- Configurable pull strategy (merge/rebase)
**Fetch:**
- `f` in files panel fetches from remote
- Auto-fetch configurable in settings
---
## Configuration
### Configuration File Location
```bash
lazygit --print-config-dir # Shows config directory
# Usually: ~/.config/lazygit/config.yml
```
### Key Configuration Options
```yaml
gui:
# Panel sizing
sidePanelWidth: 0.3333
expandFocusedSidePanel: false
mainPanelSplitMode: flexible # flexible | horizontal | vertical
# Display options
showFileTree: true
showIcons: false
nerdFontsVersion: "" # Set to "3" for nerd fonts v3
showCommandLog: true
showBottomLine: true
showPanelJumps: true
# Behavior
mouseEvents: true
skipDiscardChangeWarning: false
skipStashWarning: false
# Theme colors
theme:
activeBorderColor:
- green
- bold
inactiveBorderColor:
- default
selectedLineBgColor:
- blue
unstagedChangesColor:
- red
git:
# Auto operations
autoFetch: true
autoRefresh: true
fetchAll: true
# Commit settings
commit:
signOff: false
autoWrapCommitMessage: true
autoWrapWidth: 72
# Merge settings
merging:
manualCommit: false
args: ""
# Main branches for rebase targets
mainBranches:
- master
- main
# Log display
log:
order: topo-order # topo-order | date-order | author-date-order
showGraph: always # always | never | when-maximised
# Refresh intervals (seconds)
refresher:
refreshInterval: 10
fetchInterval: 60
# Confirmation settings
confirmOnQuit: false
quitOnTopLevelReturn: false
# Custom commands (advanced)
customCommands: []
```
### Custom Keybindings
Keybindings can be customized in `config.yml`:
```yaml
keybinding:
universal:
quit: q
return: <esc>
togglePanel: <tab>
prevItem: <up>
nextItem: <down>
# ... etc
files:
commitChanges: c
amendLastCommit: A
# ... etc
branches:
createPullRequest: o
rebaseBranch: r
# ... etc
```
### Custom Commands
Define custom commands accessible via keybindings:
```yaml
customCommands:
- key: "C"
command: "git cz" # Commitizen
context: "files"
loadingText: "Opening commitizen"
subprocess: true
- key: "b"
command: "git blame {{.SelectedFile.Name}}"
context: "files"
loadingText: "Blaming file"
```
---
## CLI Interface
### Basic Usage
```bash
lazygit # Open in current directory
lazygit -p /path/to/repo # Open specific repository
lazygit status # Focus status panel on open
lazygit branch # Focus branch panel on open
lazygit log # Focus log panel on open
lazygit stash # Focus stash panel on open
```
### Command Line Flags
| Flag | Description |
|------|-------------|
| `-h, --help` | Display help |
| `-v, --version` | Print version |
| `-p, --path PATH` | Path to git repo |
| `-f, --filter PATH` | Filter log by path |
| `-c, --config` | Print default config |
| `-cd, --print-config-dir` | Print config directory |
| `-ucd, --use-config-dir DIR` | Override config directory |
| `-ucf, --use-config-file FILES` | Comma-separated config files |
| `-w, --work-tree PATH` | Git work-tree argument |
| `-g, --git-dir PATH` | Git git-dir argument |
| `-sm, --screen-mode MODE` | Initial screen mode: normal, half, full |
| `-d, --debug` | Run in debug mode with logging |
| `-l, --logs` | Tail lazygit logs |
| `--profile` | Start profiler on port 6060 |
### Examples
```bash
# Open repo at specific path
lazygit -p ~/projects/myrepo
# Filter commits by file path
lazygit -f src/main.js
# Use custom config
lazygit -ucf ~/.config/lazygit/work-config.yml
# Start in full screen mode
lazygit -sm full
# Debug mode
lazygit -d
# In another terminal:
lazygit -l # Watch logs
```
---
## Automation with VHS
[VHS](https://github.com/charmbracelet/vhs) can be used to programmatically control lazygit for demos, testing, and documentation.
### VHS Tape Syntax
```tape
# Configure output
Output "demo.gif"
Set Shell "bash"
Set FontSize 14
Set Width 1200
Set Height 800
# Launch lazygit
Type "cd /path/to/repo && lazygit"
Enter
Sleep 2s
# Navigate with keyboard
Type "2" # Go to files panel
Sleep 500ms
Type "j" # Move down
Sleep 500ms
Space # Stage file
Sleep 500ms
Type "c" # Start commit
Sleep 1s
Type "Add new feature"
Enter
Sleep 1s
# Capture screenshots
Screenshot "commit-complete.png"
# Quit
Type "q"
```
### VHS Commands for lazygit
| Command | Usage | Description |
|---------|-------|-------------|
| `Type "x"` | `Type "c"` | Type character (triggers keybinding) |
| `Enter` | `Enter` | Press Enter key |
| `Escape` | `Escape` | Press Escape key |
| `Space` | `Space` | Press Space key |
| `Tab` | `Tab` | Press Tab key |
| `Up/Down/Left/Right` | `Down` | Arrow keys |
| `Sleep Xs` | `Sleep 500ms` | Wait for UI |
| `Screenshot "file.png"` | `Screenshot "/tmp/shot.png"` | Capture frame |
### Example: Automated Commit Demo
```tape
Output "lazygit-commit-demo.gif"
Set Shell "bash"
Set Width 1200
Set Height 800
Set Framerate 10
# Setup
Type "cd /tmp/test-repo && lazygit"
Enter
Sleep 2s
# Show modified files
Type "2"
Sleep 1s
# Stage all changes
Type "a"
Sleep 500ms
# Open commit dialog
Type "c"
Sleep 1s
# Type commit message
Type "feat: add new authentication module"
Enter
Sleep 1s
# Show result
Screenshot "commit-complete.png"
Sleep 2s
# Quit
Type "q"
```
### Running VHS Tapes
```bash
# Run tape and generate output
vhs demo.tape
# Validate tape without running
vhs validate demo.tape
# Record a new tape interactively
vhs record
# Publish GIF to share
vhs publish demo.gif
```
---
## Tips and Tricks
### Productivity Tips
1. **Use panel numbers**: Press `1-5` to jump directly to panels
2. **Toggle views**: Press `+` to maximize the current panel
3. **Filter commits**: Use `/` to search in any list
4. **Quick stash**: Press `s` to stash all changes instantly
5. **Undo mistakes**: Press `z` to undo via reflog
6. **Copy commit hash**: Press `y` on a commit for copy options
7. **Open in browser**: Press `o` on a commit to view on GitHub/GitLab
### Advanced Usage
1. **Patch mode**: Use `Ctrl+p` to create partial patches from commits
2. **Custom commands**: Define frequently used git aliases
3. **Multiple configs**: Use `-ucf` for project-specific settings
4. **Filter mode**: Use `-f` to focus on specific file history
### Keyboard Navigation Summary
```
┌─────────────────────────────────────────┐
│ PANEL NAVIGATION │
├─────────────────────────────────────────┤
│ 1-5: Jump to panel Tab: Toggle view │
│ h/l: Move left/right [/]: Switch tabs │
│ j/k: Move up/down </> : Top/bottom │
├─────────────────────────────────────────┤
│ COMMON ACTIONS │
├─────────────────────────────────────────┤
│ Space: Primary action Enter: Go into │
│ c: Commit P: Push p: Pull │
│ s: Stash z: Undo ?: Help │
│ /: Search q: Quit │
└─────────────────────────────────────────┘
```
---
## Comparison with Other Tools
| Feature | lazygit | tig | gitui | git CLI |
|---------|---------|-----|-------|---------|
| TUI Interface | Yes | Yes | Yes | No |
| Staging View | Line-level | Hunk-level | Line-level | Hunk-level |
| Interactive Rebase | Visual | No | Visual | Editor |
| Merge Conflict UI | Yes | No | Yes | No |
| Customizable | YAML config | Limited | TOML config | Aliases |
| Language | Go | C | Rust | C |
| Cross-platform | Yes | Yes | Yes | Yes |
---
## Resources
- **Documentation**: https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings
- **Configuration**: https://github.com/jesseduffield/lazygit/blob/v0.58.1/docs/Config.md
- **Tutorial Video**: https://youtu.be/VDXvbHZYeKY
- **Issues**: https://github.com/jesseduffield/lazygit/issues
- **Releases**: https://github.com/jesseduffield/lazygit/releases
- **Sponsor**: https://github.com/sponsors/jesseduffield
---
*Document generated through programmatic exploration of lazygit v0.58.1 using VHS automation.*
+10
View File
@@ -0,0 +1,10 @@
{:paths ["src" "test"]
:deps {io.github.ajet/clojure-tui {:local/root "../clojure-tui"}}
:tasks {start {:doc "Run lazygitclj"
:task (exec 'lazygitclj.core/-main)}
test {:doc "Run unit tests"
:task (load-file "test/run-tests.clj")}
test:unit {:doc "Run unit tests"
:task (load-file "test/run-tests.clj")}
test:e2e {:doc "Run e2e VHS tests"
:task (shell "test/e2e/run-all.sh")}}}
+938
View File
@@ -0,0 +1,938 @@
(ns lazygitclj.core
"lazygitclj - A lazygit-inspired TUI for git."
(:require [tui.simple :as tui]
[tui.core :refer [quit]]
[lazygitclj.git :as git]
[clojure.string :as str]))
;; === Model ===
(defn load-git-data []
{:branch (git/current-branch)
:sha (git/head-sha)
:ahead-behind (git/ahead-behind)
:staged (git/staged-files)
:unstaged (git/unstaged-files)
:commits (git/recent-commits 15)
:branches (git/branches)
:remote-branches (git/remote-branches)
:tags (git/list-tags)
:stashes (git/stash-list)
:reflog (git/reflog 50)
:diff nil})
;; initial-model is defined after helpers/update functions (needs update-diff)
;; === Helpers ===
(defn file-items [model]
(let [staged (mapv #(assoc % :type :staged) (:staged model))
unstaged (mapv #(assoc % :type :unstaged) (:unstaged model))]
(vec (concat staged unstaged))))
(defn current-items [model]
(case (:panel model)
:files (file-items model)
:commits (if (= (:commits-tab model) :reflog)
(:reflog model)
(:commits model))
:branches (case (:branches-tab model)
:local (:branches model)
:remotes (:remote-branches model)
:tags (:tags model)
(:branches model))
:stash (:stashes model)
[]))
(defn clamp-cursor [model]
(let [items (current-items model)
max-idx (max 0 (dec (count items)))]
(update model :cursor #(min max-idx (max 0 %)))))
(defn truncate [s max-len]
(if (and s (> (count s) max-len))
(str (subs s 0 max-len) "...")
s))
(defn visible-window
"Calculate the visible window of items for scrolling.
Returns {:items visible-items :start-idx start-index :total total-count}
Keeps cursor visible by scrolling the window."
[items cursor max-visible]
(let [total (count items)
max-visible (max 1 max-visible)]
(if (<= total max-visible)
;; All items fit, no scrolling needed
{:items items :start-idx 0 :total total}
;; Need to scroll - calculate window that keeps cursor visible
(let [;; Simple approach: cursor should be in the middle when possible
half (quot max-visible 2)
;; Calculate start index
start-idx (cond
;; Cursor near start - show from beginning
(<= cursor half) 0
;; Cursor near end - show end portion
(>= cursor (- total half)) (- total max-visible)
;; Cursor in middle - center it
:else (- cursor half))
start-idx (max 0 (min start-idx (- total max-visible)))
visible-items (subvec (vec items) start-idx (+ start-idx max-visible))]
{:items visible-items :start-idx start-idx :total total}))))
(defn get-current-diff
"Get the diff for the currently selected item based on panel and cursor."
[model]
(let [panel (:panel model)
items (current-items model)
cursor (:cursor model)
item (get (vec items) cursor)]
(case panel
:files
(when item
(if (= (:type item) :staged)
(git/diff-staged-file (:path item))
(git/diff-unstaged (:path item))))
:commits
(when-let [sha (:sha item)]
(git/show-commit sha))
:branches
(when item
(case (:branches-tab model)
:local (git/diff-branch item)
:remotes nil
:tags nil
nil))
:stash
(when-let [ref (:ref item)]
(git/stash-show ref))
nil)))
;; === Update ===
(defn update-diff [model]
(assoc model :diff (get-current-diff model)))
(defn initial-model []
(let [base (merge
{:panel :files
:cursor 0
:message nil
:input-mode nil
:input-buffer ""
:input-context nil
:menu-mode nil
:commits-tab :commits
:branches-tab :local
:reflog-index 0}
(load-git-data))]
(update-diff base)))
(defn refresh [model]
(-> model
(merge (load-git-data))
clamp-cursor
(assoc :message nil)
update-diff))
(defn update-files [model msg]
(let [items (file-items model)
cursor (:cursor model)
item (get items cursor)]
(cond
(tui/key= msg " ")
(if item
(do
(if (= (:type item) :staged)
(git/unstage-file (:path item))
(git/stage-file (:path item)))
[(refresh model) nil])
[model nil])
(tui/key= msg "a")
(if (and (seq (:staged model)) (empty? (:unstaged model)))
;; All files are staged - unstage all
(do
(git/reset-staged)
[(refresh model) nil])
;; Some files are unstaged - stage all
(do
(git/stage-all)
[(refresh model) nil]))
(tui/key= msg "d")
(if (and item (= (:type item) :unstaged) (not= (:index item) \?))
(do
(git/discard-file (:path item))
[(refresh model) nil])
[(assoc model :message "Can only discard unstaged changes") nil])
;; s: Quick stash all changes
(tui/key= msg "s")
(do
(git/stash-all)
[(-> model refresh (assoc :message "Stashed all changes")) nil])
;; S: Stash options menu
(tui/key= msg "S")
[(assoc model :menu-mode :stash-options) nil]
:else
[model nil])))
(defn update-commits [model msg]
(let [items (current-items model)
cursor (:cursor model)
item (get (vec items) cursor)
sha (when item (:sha item))]
(cond
;; Space: Checkout commit/reflog entry
(tui/key= msg " ")
(if sha
(do
(git/checkout-reflog-entry sha)
[(-> model refresh (assoc :message (str "Checked out " sha))) nil])
[model nil])
;; g: Reset to this commit
(tui/key= msg "g")
(if sha
(do
(git/reset-to-reflog sha)
[(-> model refresh (assoc :message (str "Reset to " sha))) nil])
[model nil])
;; C: Cherry-pick commit
(tui/key= msg "C")
(if sha
(do
(git/cherry-pick-commit sha)
[(-> model refresh (assoc :message (str "Cherry-picked " sha))) nil])
[model nil])
;; t: Revert commit
(tui/key= msg "t")
(if sha
(do
(git/revert-commit sha)
[(-> model refresh (assoc :message (str "Reverted " sha))) nil])
[model nil])
;; r: Reword commit (HEAD only)
(tui/key= msg "r")
(if sha
[(assoc model :input-mode :reword :input-buffer "" :input-context sha) nil]
[model nil])
;; y: Copy SHA
(tui/key= msg "y")
(if sha
[(assoc model :message (str "SHA: " sha " (not copied - no clipboard support)")) nil]
[model nil])
:else
[model nil])))
(defn update-branches [model msg]
(let [items (current-items model)
cursor (:cursor model)
item (get (vec items) cursor)
tab (:branches-tab model)]
(cond
;; Enter: checkout branch/tag
(tui/key= msg :enter)
(cond
(and (= tab :local) item (not= item (:branch model)))
(do
(git/checkout-branch item)
[(-> model refresh (assoc :message (str "Checked out " item))) nil])
(and (= tab :tags) item)
(do
(git/checkout-tag item)
[(-> model refresh (assoc :message (str "Checked out tag " item))) nil])
:else [model nil])
;; n: new branch (local tab only)
(and (tui/key= msg "n") (= tab :local))
[(assoc model :input-mode :new-branch :input-buffer "") nil]
;; d: delete branch (local tab only)
(and (tui/key= msg "d") (= tab :local) item (not= item (:branch model)))
(do
(git/delete-branch item)
[(-> model refresh (assoc :message (str "Deleted branch " item))) nil])
;; R: rename branch (local tab only)
(and (tui/key= msg "R") (= tab :local) item)
[(assoc model :input-mode :rename-branch :input-buffer "" :input-context item) nil]
;; M: merge branch into current
(and (tui/key= msg "M") (= tab :local) item (not= item (:branch model)))
(do
(git/merge-branch item)
[(-> model refresh (assoc :message (str "Merged " item))) nil])
;; f: fast-forward branch
(and (tui/key= msg "f") (= tab :local) item)
(do
(git/fast-forward-branch item)
[(-> model refresh (assoc :message (str "Fast-forwarded " item))) nil])
;; Tab switching with [ and ]
(tui/key= msg "[")
[(-> model
(update :branches-tab #(case % :local :tags :remotes :local :tags :remotes))
(assoc :cursor 0)
clamp-cursor) nil]
(tui/key= msg "]")
[(-> model
(update :branches-tab #(case % :local :remotes :remotes :tags :tags :local))
(assoc :cursor 0)
clamp-cursor) nil]
:else
[model nil])))
(defn update-stash [model msg]
(let [stashes (:stashes model)
cursor (:cursor model)
stash (get (vec stashes) cursor)
ref (when stash (:ref stash))]
(cond
;; Space: apply stash (keep in list)
(tui/key= msg " ")
(if ref
(do
(git/stash-apply ref)
[(-> model refresh (assoc :message (str "Applied " ref))) nil])
[model nil])
;; g: pop stash (apply and remove)
(tui/key= msg "g")
(if ref
(do
(git/stash-pop ref)
[(-> model refresh (assoc :message (str "Popped " ref))) nil])
[model nil])
;; d: drop stash
(tui/key= msg "d")
(if ref
(do
(git/stash-drop ref)
[(-> model refresh (assoc :message (str "Dropped " ref))) nil])
[model nil])
;; n: new branch from stash
(and (tui/key= msg "n") ref)
[(assoc model :input-mode :stash-branch :input-buffer "" :input-context ref) nil]
:else
[model nil])))
(defn update-input-mode [model msg]
(cond
(tui/key= msg :escape)
[(assoc model :input-mode nil :input-buffer "" :input-context nil) nil]
(tui/key= msg :enter)
(let [buf (:input-buffer model)
ctx (:input-context model)]
(case (:input-mode model)
:commit
(if (str/blank? buf)
[(assoc model :message "Commit message cannot be empty") nil]
(do
(git/commit buf)
[(-> model
(assoc :input-mode nil :input-buffer "" :input-context nil)
refresh
(assoc :message "Committed!")) nil]))
:new-branch
(if (str/blank? buf)
[(assoc model :message "Branch name cannot be empty") nil]
(do
(git/create-branch buf)
[(-> model
(assoc :input-mode nil :input-buffer "" :input-context nil)
refresh
(assoc :message (str "Created branch " buf))) nil]))
:rename-branch
(if (str/blank? buf)
[(assoc model :message "New name cannot be empty") nil]
(do
(git/rename-branch ctx buf)
[(-> model
(assoc :input-mode nil :input-buffer "" :input-context nil)
refresh
(assoc :message (str "Renamed " ctx " to " buf))) nil]))
:stash-branch
(if (str/blank? buf)
[(assoc model :message "Branch name cannot be empty") nil]
(do
(git/stash-branch buf ctx)
[(-> model
(assoc :input-mode nil :input-buffer "" :input-context nil)
refresh
(assoc :message (str "Created branch " buf " from stash"))) nil]))
:reword
(if (str/blank? buf)
[(assoc model :message "Message cannot be empty") nil]
(if (git/reword-commit ctx buf)
[(-> model
(assoc :input-mode nil :input-buffer "" :input-context nil)
refresh
(assoc :message "Rewrote commit message")) nil]
[(assoc model :input-mode nil :input-buffer "" :input-context nil
:message "Can only reword HEAD commit") nil]))
[model nil]))
(tui/key= msg :backspace)
[(update model :input-buffer #(if (empty? %) % (subs % 0 (dec (count %))))) nil]
;; Character input - key format is [:key {:char \a}]
(and (= (first msg) :key)
(map? (second msg))
(:char (second msg))
(not (:ctrl (second msg)))
(not (:alt (second msg))))
[(update model :input-buffer str (:char (second msg))) nil]
:else
[model nil]))
(defn update-stash-menu [model msg]
(cond
(tui/key= msg :escape)
[(assoc model :menu-mode nil) nil]
;; a: Stash all changes
(tui/key= msg "a")
(do
(git/stash-all)
[(-> model (assoc :menu-mode nil) refresh (assoc :message "Stashed all changes")) nil])
;; i: Stash all changes and keep index
(tui/key= msg "i")
(do
(git/stash-keep-index)
[(-> model (assoc :menu-mode nil) refresh (assoc :message "Stashed all changes (kept index)")) nil])
;; U: Stash all including untracked files
(tui/key= msg "U")
(do
(git/stash-include-untracked)
[(-> model (assoc :menu-mode nil) refresh (assoc :message "Stashed all changes (including untracked)")) nil])
;; s: Stash staged changes only
(tui/key= msg "s")
(do
(git/stash-staged)
[(-> model (assoc :menu-mode nil) refresh (assoc :message "Stashed staged changes only")) nil])
;; u: Stash unstaged changes only
(tui/key= msg "u")
(do
(git/stash-unstaged)
[(-> model (assoc :menu-mode nil) refresh (assoc :message "Stashed unstaged changes only")) nil])
:else
[model nil]))
(defn update-reset-menu [model msg]
(cond
(tui/key= msg :escape)
[(assoc model :menu-mode nil) nil]
;; s: Soft reset (keep staged)
(tui/key= msg "s")
(do
(git/reset-soft)
[(-> model (assoc :menu-mode nil) refresh (assoc :message "Soft reset to HEAD~1")) nil])
;; m: Mixed reset (unstage)
(tui/key= msg "m")
(do
(git/reset-mixed)
[(-> model (assoc :menu-mode nil) refresh (assoc :message "Mixed reset to HEAD~1")) nil])
;; h: Hard reset (discard all)
(tui/key= msg "h")
(do
(git/reset-hard)
[(-> model (assoc :menu-mode nil) refresh (assoc :message "Hard reset to HEAD~1")) nil])
;; u: Unstage all
(tui/key= msg "u")
(do
(git/reset-staged)
[(-> model (assoc :menu-mode nil) refresh (assoc :message "Unstaged all changes")) nil])
;; d: Discard unstaged changes
(tui/key= msg "d")
(do
(git/discard-all-unstaged)
[(-> model (assoc :menu-mode nil) refresh (assoc :message "Discarded unstaged changes")) nil])
;; c: Clean untracked files
(tui/key= msg "c")
(do
(git/clean-untracked)
[(-> model (assoc :menu-mode nil) refresh (assoc :message "Cleaned untracked files")) nil])
:else
[model nil]))
(defn update-help [model msg]
(cond
(or (tui/key= msg :escape) (tui/key= msg "?") (tui/key= msg "q"))
[(assoc model :menu-mode nil) nil]
:else
[model nil]))
(defn update-model [model msg]
(cond
;; Menu modes take priority
(= (:menu-mode model) :stash-options)
(update-stash-menu model msg)
(= (:menu-mode model) :reset-options)
(update-reset-menu model msg)
(= (:menu-mode model) :help)
(update-help model msg)
(:input-mode model)
(update-input-mode model msg)
(or (tui/key= msg "q") (tui/key= msg [:ctrl \c]))
[model quit]
(tui/key= msg "r")
[(refresh model) nil]
;; Help panel
(tui/key= msg "?")
[(assoc model :menu-mode :help) nil]
;; Reset options menu
(tui/key= msg "D")
[(assoc model :menu-mode :reset-options) nil]
;; Panel jump keys (matching lazygit: 2=Files, 3=Branches, 4=Commits, 5=Stash)
(tui/key= msg "2")
[(-> model (assoc :panel :files :cursor 0) clamp-cursor update-diff) nil]
(tui/key= msg "3")
[(-> model (assoc :panel :branches :cursor 0) clamp-cursor update-diff) nil]
(tui/key= msg "4")
[(-> model (assoc :panel :commits :cursor 0) clamp-cursor update-diff) nil]
(tui/key= msg "5")
[(-> model (assoc :panel :stash :cursor 0) clamp-cursor update-diff) nil]
;; h/l or Left/Right: move between panels (order: files → branches → commits → stash)
(or (tui/key= msg "h") (tui/key= msg :left))
[(-> model
(update :panel #(case % :files :stash :branches :files :commits :branches :stash :commits))
(assoc :cursor 0)
clamp-cursor
update-diff) nil]
(or (tui/key= msg "l") (tui/key= msg :right))
[(-> model
(update :panel #(case % :files :branches :branches :commits :commits :stash :stash :files))
(assoc :cursor 0)
clamp-cursor
update-diff) nil]
(or (tui/key= msg :up) (tui/key= msg "k"))
[(-> model (update :cursor dec) clamp-cursor update-diff) nil]
(or (tui/key= msg :down) (tui/key= msg "j"))
[(-> model (update :cursor inc) clamp-cursor update-diff) nil]
(and (tui/key= msg "c") (= (:panel model) :files))
(if (empty? (:staged model))
[(assoc model :message "Nothing staged to commit") nil]
[(assoc model :input-mode :commit :input-buffer "") nil])
(tui/key= msg "P")
(do
(git/push)
[(assoc model :message "Pushed!") nil])
(tui/key= msg "p")
(do
(git/pull)
[(-> model refresh (assoc :message "Pulled!")) nil])
;; Tab switching with [ and ] (for commits panel)
(and (tui/key= msg "[") (= (:panel model) :commits))
[(-> model
(update :commits-tab #(if (= % :reflog) :commits :reflog))
(assoc :cursor 0)
clamp-cursor) nil]
(and (tui/key= msg "]") (= (:panel model) :commits))
[(-> model
(update :commits-tab #(if (= % :commits) :reflog :commits))
(assoc :cursor 0)
clamp-cursor) nil]
;; Global undo (z): Reset to previous reflog entry
(tui/key= msg "z")
(let [reflog (:reflog model)
current-idx (or (:reflog-index model) 0)
next-idx (inc current-idx)]
(if (< next-idx (count reflog))
(let [entry (get (vec reflog) next-idx)]
(git/reset-to-reflog (:ref entry))
[(-> model
refresh
(assoc :reflog-index next-idx)
(assoc :message (str "Undo: reset to " (:ref entry)))) nil])
[(assoc model :message "Nothing to undo") nil]))
;; Global redo (Z): Reset forward in reflog
(tui/key= msg "Z")
(let [current-idx (or (:reflog-index model) 0)]
(if (> current-idx 0)
(let [prev-idx (dec current-idx)
reflog (:reflog model)
entry (get (vec reflog) prev-idx)]
(git/reset-to-reflog (:ref entry))
[(-> model
refresh
(assoc :reflog-index prev-idx)
(assoc :message (str "Redo: reset to " (:ref entry)))) nil])
[(assoc model :message "Nothing to redo") nil]))
:else
(case (:panel model)
:files (update-files model msg)
:commits (update-commits model msg)
:branches (update-branches model msg)
:stash (update-stash model msg)
[model nil])))
;; === Views ===
(defn file-status-char [{:keys [index worktree]}]
(str index worktree))
;; Panel 1: Status
(defn status-panel [{:keys [branch sha ahead-behind panel]}]
(let [[ahead behind] ahead-behind
sync-info (when (and ahead behind (or (pos? ahead) (pos? behind)))
(str "↑" ahead "↓" behind))]
[:box {:border (if (= panel :status) :double :single)
:title "1 Status" :padding [0 1] :width :fill :height :fill}
(into [:row {:gap 1}]
(remove nil?
[[:text {:fg :magenta :bold true} (truncate (or branch "?") 12)]
[:text "→"]
[:text {:fg :yellow} (or sha "?")]
(when sync-info [:text {:fg :cyan} sync-info])]))]))
;; Panel 2: Files
(defn files-panel [{:keys [staged unstaged cursor panel]} max-visible]
(let [active? (= panel :files)
all-files (into (mapv #(assoc % :section :staged) staged)
(mapv #(assoc % :section :unstaged) unstaged))
{:keys [items start-idx total]} (visible-window all-files (if active? cursor 0) max-visible)]
[:box {:border (if active? :double :single)
:title (str "2 Files (" total ")")
:padding [0 1] :width :fill :height :fill}
(if (empty? all-files)
[:text {:fg :gray} "No changes"]
(into [:col]
(for [[local-idx file] (map-indexed vector items)]
(let [global-idx (+ start-idx local-idx)
is-cursor (and active? (= global-idx cursor))
color (if (= (:section file) :staged) :green :red)]
[:row {:gap 1}
[:text {:fg color} (file-status-char file)]
[:text {:fg (if is-cursor :cyan color) :bold is-cursor :inverse is-cursor}
(truncate (:path file) 20)]]))))]))
;; Panel 3: Branches
(defn branches-panel [{:keys [branches remote-branches tags branch branches-tab cursor panel]} max-visible]
(let [active? (= panel :branches)
all-items (case branches-tab :local branches :remotes remote-branches :tags tags branches)
{:keys [items start-idx total]} (visible-window all-items (if active? cursor 0) max-visible)
tab-str (case branches-tab :local "[L] R T" :remotes "L [R] T" :tags "L R [T]")]
[:box {:border (if active? :double :single)
:title (str "3 Branches " tab-str)
:padding [0 1] :width :fill :height :fill}
(if (empty? all-items)
[:text {:fg :gray} (str "No " (name branches-tab))]
(into [:col]
(for [[local-idx item] (map-indexed vector items)]
(let [global-idx (+ start-idx local-idx)
is-cursor (and active? (= global-idx cursor))
is-current (and (= branches-tab :local) (= item branch))
fg (cond is-current :green is-cursor :cyan :else :white)]
[:text {:fg fg :bold (or is-current is-cursor) :inverse is-cursor}
(str (if is-current "* " " ") (truncate item 22))]))))]))
;; Panel 4: Commits
(defn commits-panel [{:keys [commits reflog commits-tab cursor panel]} max-visible]
(let [active? (= panel :commits)
reflog? (= commits-tab :reflog)
all-items (if reflog? reflog commits)
{:keys [items start-idx total]} (visible-window all-items (if active? cursor 0) max-visible)]
[:box {:border (if active? :double :single)
:title (str "4 Commits " (if reflog? "C [R]" "[C] R"))
:padding [0 1] :width :fill :height :fill}
(if (empty? all-items)
[:text {:fg :gray} "No commits"]
(into [:col]
(for [[local-idx {:keys [sha action subject]}] (map-indexed vector items)]
(let [global-idx (+ start-idx local-idx)
is-cursor (and active? (= global-idx cursor))]
[:row {:gap 1}
[:text {:fg :yellow} (or sha "?")]
[:text {:fg (if is-cursor :cyan :white) :bold is-cursor :inverse is-cursor}
(truncate (if reflog? (str action ": " subject) (or subject "")) 14)]]))))]))
;; Panel 5: Stash
(defn stash-panel [{:keys [stashes cursor panel]} max-visible]
(let [active? (= panel :stash)
{:keys [items start-idx total]} (visible-window stashes (if active? cursor 0) max-visible)]
[:box {:border (if active? :double :single)
:title (str "5 Stash (" total ")")
:padding [0 1] :width :fill :height :fill}
(if (empty? stashes)
[:text {:fg :gray} "No stashes"]
(into [:col]
(for [[local-idx {:keys [index message]}] (map-indexed vector items)]
(let [global-idx (+ start-idx local-idx)
is-cursor (and active? (= global-idx cursor))]
[:row {:gap 1}
[:text {:fg :yellow} (str (or index global-idx))]
[:text {:fg (if is-cursor :cyan :white) :bold is-cursor :inverse is-cursor}
(truncate (or message "") 20)]]))))]))
;; Panel 0: Main View (Diff)
(defn main-view-panel [{:keys [diff]}]
(let [lines (when diff (str/split-lines diff))]
[:box {:border :single :title "0 Main" :padding [0 1] :width :fill :height :fill}
(if (empty? lines)
[:text {:fg :gray} "Select an item to view diff"]
[:col
(for [line lines]
[:text {:fg (cond
(str/starts-with? line "+") :green
(str/starts-with? line "-") :red
(str/starts-with? line "@@") :cyan
(or (str/starts-with? line "diff")
(str/starts-with? line "commit")) :yellow
:else :white)}
line])])]))
;; Command Log (placeholder)
(defn command-log-panel []
[:box {:border :single :title "Command Log" :padding [0 1] :width :fill :height :fill}
[:text {:fg :gray} ""]])
;; Bottom help bar
(defn help-bar [model]
(let [panel (:panel model)
panel-help (case panel
:files "spc:stage a:all c:commit"
:commits "[]:tabs spc:checkout"
:branches "[]:tabs n:new d:del"
:stash "spc:apply g:pop d:drop"
"")]
(into [:row {:gap 1}]
(remove nil?
[[:text {:fg :gray} "q:quit"]
[:text {:fg :gray} "?:help"]
[:text {:fg :gray} "h/l:panels"]
[:text {:fg :gray} "j/k:nav"]
(when (seq panel-help)
[:text {:fg :gray} panel-help])
[:text {:fg :gray} "p/P:pull/push"]]))))
(defn stash-menu-view [{:keys [menu-mode]}]
(when (= menu-mode :stash-options)
[:box {:border :double :title "Stash Options" :padding [0 1]}
[:col
[:text {:fg :cyan} "a - Stash all changes"]
[:text {:fg :cyan} "i - Stash all changes and keep index"]
[:text {:fg :cyan} "U - Stash all including untracked files"]
[:text {:fg :yellow} "s - Stash staged changes only"]
[:text {:fg :yellow} "u - Stash unstaged changes only"]
[:text ""]
[:text {:fg :gray} "esc - Cancel"]]]))
(defn reset-menu-view [{:keys [menu-mode]}]
(when (= menu-mode :reset-options)
[:box {:border :double :title "Reset Options" :padding [0 1]}
[:col
[:text {:fg :yellow} "s - Soft reset (uncommit, keep staged)"]
[:text {:fg :yellow} "m - Mixed reset (uncommit, unstage)"]
[:text {:fg :red} "h - Hard reset (discard all changes)"]
[:text ""]
[:text {:fg :cyan} "u - Unstage all staged changes"]
[:text {:fg :cyan} "d - Discard all unstaged changes"]
[:text {:fg :red} "c - Clean untracked files"]
[:text ""]
[:text {:fg :gray} "esc - Cancel"]]]))
(defn help-view [{:keys [menu-mode]}]
(when (= menu-mode :help)
[:box {:border :double :title "Help - Keybindings" :padding [0 1]}
[:col
[:text {:fg :cyan :bold true} "Global:"]
[:text " q - Quit r - Refresh"]
[:text " h/l - Prev/Next panel 2-5 - Jump to panel"]
[:text " j/k - Move down/up z/Z - Undo/Redo"]
[:text " p - Pull P - Push"]
[:text " ? - Help D - Reset options"]
[:text ""]
[:text {:fg :green :bold true} "Files (2):"]
[:text " space - Stage/unstage a - Stage all"]
[:text " c - Commit d - Discard file"]
[:text " s - Quick stash S - Stash options"]
[:text ""]
[:text {:fg :yellow :bold true} "Branches (3):"]
[:text " [/] - Switch tabs enter - Checkout"]
[:text " n - New branch d - Delete branch"]
[:text " R - Rename M - Merge"]
[:text " f - Fast-forward"]
[:text ""]
[:text {:fg :magenta :bold true} "Commits (4):"]
[:text " [/] - Switch tabs space - Checkout"]
[:text " g - Reset to C - Cherry-pick"]
[:text " t - Revert r - Reword (HEAD only)"]
[:text " y - Show SHA"]
[:text ""]
[:text {:fg :blue :bold true} "Stash (5):"]
[:text " space - Apply g - Pop (apply+drop)"]
[:text " d - Drop n - Branch from stash"]
[:text ""]
[:text {:fg :gray} "Press ?, q, or esc to close"]]]))
(defn input-view [{:keys [input-mode input-buffer input-context]}]
(when input-mode
(let [title (case input-mode
:commit "Commit Message"
:new-branch "New Branch Name"
:rename-branch (str "Rename " input-context " to")
:stash-branch "Branch Name from Stash"
:reword "Reword Commit Message"
(name input-mode))]
[:box {:border :rounded :title title :padding [0 1] :width 50}
[:input {:value input-buffer}]])))
(defn message-view [{:keys [message]}]
(when message
[:text {:fg :yellow :bold true} (str " " message)]))
(defn main-grid-view
"Render the main lazygit-style grid layout."
[model width height]
(let [has-message? (some? (:message model))
narrow? (< width 70)
;; Calculate available height for panels
;; Wide layout: left column has [3 :flex :flex :flex :flex]
;; So 4 flex panels share (height - status(3) - help-bar(1) - message?)
;; Each panel total height = remaining / 4
;; Inner height = panel total - 2 (borders)
remaining-height (- height 3 1 (if has-message? 1 0))
panel-total-height (quot remaining-height 4)
;; Inner height is total minus borders (2 rows)
panel-inner-height (if narrow?
;; Narrow: 6 sections share height
(max 1 (- (quot remaining-height 6) 2))
;; Wide: 4 panels share remaining height
(max 1 (- panel-total-height 2)))
content (if narrow?
;; NARROW: Single column stacked
[:col {:heights [3 :flex :flex :flex 3 :flex 4]}
(status-panel model)
(files-panel model panel-inner-height)
(branches-panel model panel-inner-height)
(commits-panel model panel-inner-height)
(stash-panel model panel-inner-height)
(main-view-panel model)
(command-log-panel)]
;; WIDE: Two columns
[:row {:gap 1 :widths [30 :flex]}
[:col {:heights [3 :flex :flex :flex :flex]}
(status-panel model)
(files-panel model panel-inner-height)
(branches-panel model panel-inner-height)
(commits-panel model panel-inner-height)
(stash-panel model panel-inner-height)]
[:col {:heights [:flex 4]}
(main-view-panel model)
(command-log-panel)]])]
(if has-message?
[:col {:width width :height height :heights [1 :flex 1]}
(message-view model)
content
(help-bar model)]
[:col {:width width :height height :heights [:flex 1]}
content
(help-bar model)])))
(defn view [model {:keys [width height] :or {width 120 height 30}}]
(let [background (main-grid-view model width height)]
(cond
;; Help menu modal overlay
(= (:menu-mode model) :help)
[:modal {}
background
(help-view model)]
;; Stash options menu modal overlay
(= (:menu-mode model) :stash-options)
[:modal {}
background
(stash-menu-view model)]
;; Reset options menu modal overlay
(= (:menu-mode model) :reset-options)
[:modal {}
background
(reset-menu-view model)]
;; Input mode modal overlay (commit message, etc.)
(:input-mode model)
[:modal {}
background
(input-view model)]
;; Default grid view
:else
background)))
;; === Main ===
(defn -main [& _args]
(if (git/repo-root)
(do
(println "Starting lazygitclj...")
(tui/run {:init (initial-model)
:update update-model
:view view})
(println "Goodbye!"))
(do
(println "Error: Not a git repository")
(System/exit 1))))
+428
View File
@@ -0,0 +1,428 @@
(ns lazygitclj.git
"Git operations via shell commands."
(:require [babashka.process :refer [shell]]
[clojure.string :as str]))
(defn- sh
"Run shell command, return stdout or nil on error."
[& args]
(try
(-> (apply shell {:out :string :err :string} args)
:out
str/trim-newline)
(catch Exception _ nil)))
(defn- lines [s]
(if (str/blank? s) [] (str/split-lines s)))
;; === Status ===
(defn current-branch []
(sh "git" "branch" "--show-current"))
(defn head-sha []
(sh "git" "rev-parse" "--short" "HEAD"))
(defn repo-root []
(sh "git" "rev-parse" "--show-toplevel"))
(defn ahead-behind
"Returns [ahead behind] commit counts relative to upstream."
[]
(when-let [out (sh "git" "rev-list" "--left-right" "--count" "HEAD...@{upstream}")]
(let [[ahead behind] (str/split out #"\t")]
[(parse-long ahead) (parse-long behind)])))
;; === Files ===
(defn- parse-status-line [line]
(when (>= (count line) 3)
(let [index (nth line 0)
worktree (nth line 1)
path (subs line 3)]
{:index index
:worktree worktree
:path path})))
(defn status-files
"Returns list of files with their status."
[]
(->> (sh "git" "status" "--porcelain")
lines
(map parse-status-line)
(remove nil?)))
(defn staged-files []
(->> (status-files)
(filter #(not= (:index %) \space))
(filter #(not= (:index %) \?))))
(defn unstaged-files []
(->> (status-files)
(filter #(or (not= (:worktree %) \space)
(= (:index %) \?)))))
(defn untracked-files []
(->> (status-files)
(filter #(= (:index %) \?))))
;; === Commits ===
(defn- parse-log-line [line]
(let [[sha date author subject] (str/split line #"\|" 4)]
{:sha sha
:date date
:author author
:subject subject}))
(defn recent-commits
"Returns recent commits."
([] (recent-commits 20))
([n]
(->> (sh "git" "log" (str "-" n) "--pretty=format:%h|%cr|%an|%s")
lines
(map parse-log-line))))
;; === Branches ===
(defn branches
"Returns list of local branches."
[]
(->> (sh "git" "branch" "--format=%(refname:short)")
lines))
(defn remote-branches []
(->> (sh "git" "branch" "-r" "--format=%(refname:short)")
lines))
;; === Actions ===
(defn stage-file [path]
(sh "git" "add" path))
(defn unstage-file [path]
(sh "git" "reset" "HEAD" path))
(defn stage-all []
(sh "git" "add" "-A"))
(defn checkout-branch [branch]
(sh "git" "checkout" branch))
(defn create-branch [name]
(sh "git" "checkout" "-b" name))
(defn commit [message]
(sh "git" "commit" "-m" message))
(defn discard-file [path]
(sh "git" "checkout" "--" path))
(defn diff-file [path]
(sh "git" "diff" path))
(defn diff-staged [path]
(sh "git" "diff" "--cached" path))
(defn pull []
(sh "git" "pull"))
(defn push []
(sh "git" "push"))
;; === Diffs ===
(defn diff-unstaged
"Show unstaged changes for a file, or all if no path given."
([] (sh "git" "diff"))
([path] (sh "git" "diff" "--" path)))
(defn diff-staged-file
"Show staged changes for a file, or all if no path given."
([] (sh "git" "diff" "--cached"))
([path] (sh "git" "diff" "--cached" "--" path)))
(defn show-commit
"Show commit details and diff."
[sha]
(sh "git" "show" "--stat" "--patch" sha))
(defn diff-branch
"Show diff between current branch and another branch."
[branch]
(sh "git" "diff" (str branch "...HEAD") "--stat"))
;; === Reset Operations ===
(defn discard-all-unstaged
"Discard all unstaged changes (git checkout -- .)."
[]
(sh "git" "checkout" "--" "."))
(defn clean-untracked
"Remove untracked files (git clean -fd)."
[]
(sh "git" "clean" "-fd"))
(defn reset-staged
"Unstage all staged changes (git reset HEAD)."
[]
(sh "git" "reset" "HEAD"))
(defn reset-soft
"Soft reset to previous commit (git reset --soft HEAD~1)."
[]
(sh "git" "reset" "--soft" "HEAD~1"))
(defn reset-mixed
"Mixed reset to previous commit (git reset --mixed HEAD~1)."
[]
(sh "git" "reset" "--mixed" "HEAD~1"))
(defn reset-hard
"Hard reset to previous commit (git reset --hard HEAD~1)."
[]
(sh "git" "reset" "--hard" "HEAD~1"))
;; === Commit Operations ===
(defn get-full-sha
"Get the full SHA for a short SHA."
[short-sha]
(sh "git" "rev-parse" short-sha))
(defn revert-commit
"Create a revert commit for the given SHA.
Uses --no-edit to avoid interactive prompts."
[sha]
(sh "git" "revert" "--no-edit" sha))
(defn cherry-pick-commit
"Cherry-pick the given commit SHA onto the current branch."
[sha]
(sh "git" "cherry-pick" sha))
(defn reword-commit
"Reword the commit message for a given SHA.
Note: This only works for the HEAD commit using --amend.
For non-HEAD commits, interactive rebase is required which
cannot be automated non-interactively. Returns success only for HEAD."
[sha new-message]
(let [head-sha (sh "git" "rev-parse" "--short" "HEAD")]
(if (= sha head-sha)
;; Can only reword HEAD commit non-interactively
(sh "git" "commit" "--amend" "-m" new-message)
;; For non-HEAD commits, we'd need interactive rebase
;; which isn't practical in a TUI context
nil)))
;; === Stash ===
(defn- parse-stash-line [line]
(when-let [[_ index branch message] (re-matches #"stash@\{(\d+)\}: (On [^:]+|WIP on [^:]+): (.+)" line)]
{:index (parse-long index)
:ref (str "stash@{" index "}")
:branch (str/replace branch #"^(On |WIP on )" "")
:message message}))
(defn stash-list
"Returns list of stash entries."
[]
(->> (sh "git" "stash" "list")
lines
(map parse-stash-line)
(remove nil?)))
(defn stash-push
"Stash all changes with optional message."
([] (sh "git" "stash" "push"))
([message] (sh "git" "stash" "push" "-m" message)))
(defn stash-pop
"Apply stash and remove it from stash list."
([] (sh "git" "stash" "pop"))
([stash-ref] (sh "git" "stash" "pop" stash-ref)))
(defn stash-apply
"Apply stash but keep it in stash list."
([] (sh "git" "stash" "apply"))
([stash-ref] (sh "git" "stash" "apply" stash-ref)))
(defn stash-drop
"Remove a stash entry from stash list."
([] (sh "git" "stash" "drop"))
([stash-ref] (sh "git" "stash" "drop" stash-ref)))
(defn stash-show
"Show stash diff."
([] (sh "git" "stash" "show" "-p"))
([stash-ref] (sh "git" "stash" "show" "-p" stash-ref)))
(defn stash-branch
"Create a new branch from a stash entry."
([branch-name] (sh "git" "stash" "branch" branch-name))
([branch-name stash-ref] (sh "git" "stash" "branch" branch-name stash-ref)))
(defn stash-all
"Stash all changes (git stash push). Alias for stash-push."
[]
(sh "git" "stash" "push"))
(defn stash-keep-index
"Stash all changes but keep staged changes in the index (git stash push --keep-index)."
[]
(sh "git" "stash" "push" "--keep-index"))
(defn stash-include-untracked
"Stash all changes including untracked files (git stash push --include-untracked)."
[]
(sh "git" "stash" "push" "--include-untracked"))
(defn stash-staged
"Stash only staged changes (git stash push --staged). Requires git 2.35+."
[]
(sh "git" "stash" "push" "--staged"))
(defn stash-unstaged
"Stash only unstaged changes.
Uses double-stash technique: stash with --keep-index (stashes all, keeps staged in worktree),
then we have only staged changes left. Stash those temporarily, apply the first stash
to restore all changes, drop the staged stash. Finally stash with --keep-index again
to get only unstaged in stash."
[]
;; Simpler approach: git stash push --keep-index stashes everything but keeps staged in worktree
;; The resulting stash contains unstaged changes. But wait - that's not quite right either.
;;
;; Actually --keep-index: stashes both staged AND unstaged, but leaves staged in worktree.
;; So the stash has ALL changes, not just unstaged.
;;
;; Correct approach for "stash unstaged only":
;; 1. Stash all with --keep-index (stash has all, worktree has staged)
;; 2. Stash again (stash has staged from worktree)
;; 3. Pop stash@{1} (restore all changes to worktree)
;; 4. Stash with --keep-index (now stash has all, worktree has staged)
;; 5. Drop stash@{1} (the staged-only stash we don't need)
;; Result: stash@{0} has all changes (not what we want)
;;
;; Different approach - stage everything first, then use --staged:
;; No, that defeats the purpose.
;;
;; Best approach: commit staged, stash unstaged, reset soft
;; 1. Commit staged changes with temp message
;; 2. Stash remaining (unstaged) changes
;; 3. Reset --soft HEAD~1 to uncommit but keep staged
(let [has-staged (not (str/blank? (sh "git" "diff" "--cached" "--stat")))]
(if has-staged
(do
;; Commit staged temporarily
(sh "git" "commit" "-m" "__lazygitclj_temp_commit__")
;; Stash unstaged changes
(sh "git" "stash" "push")
;; Reset soft to restore staged (uncommit)
(sh "git" "reset" "--soft" "HEAD~1"))
;; No staged changes, just stash everything
(sh "git" "stash" "push"))))
;; === Branch Operations ===
(defn delete-branch
"Delete a local branch. Returns nil if branch cannot be deleted (e.g., checked out)."
[branch]
(sh "git" "branch" "-d" branch))
(defn delete-branch-force
"Force delete a local branch."
[branch]
(sh "git" "branch" "-D" branch))
(defn rename-branch
"Rename a branch."
[old-name new-name]
(sh "git" "branch" "-m" old-name new-name))
(defn merge-branch
"Merge a branch into the current branch."
[branch]
(sh "git" "merge" branch))
(defn can-fast-forward?
"Check if branch can be fast-forwarded to its upstream.
Returns true if branch is behind upstream and can be fast-forwarded."
[branch]
(let [upstream (sh "git" "rev-parse" "--abbrev-ref" (str branch "@{upstream}"))
;; Get merge-base between branch and upstream
merge-base (when upstream
(sh "git" "merge-base" branch upstream))
;; Get the commit SHA of the branch
branch-sha (sh "git" "rev-parse" branch)]
;; Can fast-forward if branch is at the merge-base (i.e., upstream is ahead)
(and merge-base branch-sha (= merge-base branch-sha))))
(defn fast-forward-branch
"Fast-forward a branch to its upstream. Works for non-current branches too."
[branch]
(let [current (current-branch)]
(if (= branch current)
;; If on the branch, use pull --ff-only
(sh "git" "pull" "--ff-only")
;; If not on the branch, fetch and update ref
(let [upstream (sh "git" "rev-parse" "--abbrev-ref" (str branch "@{upstream}"))]
(when upstream
(sh "git" "fetch" "origin" (str branch ":" branch)))))))
;; === Reflog Operations ===
(defn- parse-reflog-line [line]
(when-let [[_ sha ref-selector action subject] (re-matches #"([a-f0-9]+) (HEAD@\{\d+\}): ([^:]+): ?(.*)" line)]
{:sha sha
:ref ref-selector
:action action
:subject (if (str/blank? subject) action subject)}))
(defn reflog
"Returns reflog entries."
([] (reflog 50))
([n]
(->> (sh "git" "reflog" "-n" (str n))
lines
(map parse-reflog-line)
(remove nil?))))
(defn checkout-reflog-entry
"Checkout a specific reflog entry by its SHA."
[sha]
(sh "git" "checkout" sha))
(defn reset-to-reflog
"Reset to a specific reflog entry (git reset --hard <ref>)."
[ref]
(sh "git" "reset" "--hard" ref))
;; === Tags ===
(defn list-tags
"Returns list of tags."
[]
(->> (sh "git" "tag" "--sort=-creatordate")
lines))
(defn create-tag
"Create a new tag."
([name] (sh "git" "tag" name))
([name message] (sh "git" "tag" "-a" name "-m" message)))
(defn delete-tag
"Delete a tag locally."
[name]
(sh "git" "tag" "-d" name))
(defn push-tag
"Push a tag to remote."
([name] (push-tag name "origin"))
([name remote]
(sh "git" "push" remote name)))
(defn checkout-tag
"Checkout a tag (detached HEAD)."
[name]
(sh "git" "checkout" name))
@@ -0,0 +1,15 @@
# VHS tape for lazygit - Classic 4:3 aspect ratio (1024x768)
Output lazygit-classic-4-3.gif
Set Shell "bash"
Set Width 1024
Set Height 768
Set FontSize 14
Set Framerate 10
Type "cd /home/ajet/repos/lazygitclj && lazygit"
Enter
Sleep 5s
Screenshot lazygit-classic-4-3.png
Type "q"
Sleep 500ms
@@ -0,0 +1,15 @@
# VHS tape for lazygit - Standard 16:9 aspect ratio (1280x720)
Output lazygit-standard-16-9.gif
Set Shell "bash"
Set Width 1280
Set Height 720
Set FontSize 14
Set Framerate 10
Type "cd /home/ajet/repos/lazygitclj && lazygit"
Enter
Sleep 5s
Screenshot lazygit-standard-16-9.png
Type "q"
Sleep 500ms
@@ -0,0 +1,15 @@
# VHS tape for lazygit - Tall 9:16 aspect ratio (600x1000)
Output lazygit-tall-9-16.gif
Set Shell "bash"
Set Width 600
Set Height 1000
Set FontSize 14
Set Framerate 10
Type "cd /home/ajet/repos/lazygitclj && lazygit"
Enter
Sleep 5s
Screenshot lazygit-tall-9-16.png
Type "q"
Sleep 500ms
@@ -0,0 +1,15 @@
# VHS tape for lazygit - Ultrawide 32:9 aspect ratio (1600x450)
Output lazygit-ultrawide-32-9.gif
Set Shell "bash"
Set Width 1600
Set Height 450
Set FontSize 14
Set Framerate 10
Type "cd /home/ajet/repos/lazygitclj && lazygit"
Enter
Sleep 5s
Screenshot lazygit-ultrawide-32-9.png
Type "q"
Sleep 500ms
@@ -0,0 +1,15 @@
# VHS tape for lazygit - Wide 21:9 aspect ratio (1400x600)
Output lazygit-wide-21-9.gif
Set Shell "bash"
Set Width 1400
Set Height 600
Set FontSize 14
Set Framerate 10
Type "cd /home/ajet/repos/lazygitclj && lazygit"
Enter
Sleep 5s
Screenshot lazygit-wide-21-9.png
Type "q"
Sleep 500ms
@@ -0,0 +1,17 @@
# VHS tape for lazygitclj - Classic 4:3 aspect ratio (1024x768)
Output lazygitclj-classic-4-3.gif
Require bb
Set Shell "bash"
Set Width 1024
Set Height 768
Set FontSize 14
Set Framerate 10
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-ar && cd /tmp/lazygitclj-e2e-ar && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 3s
Screenshot lazygitclj-classic-4-3.png
Type "q"
Sleep 500ms
@@ -0,0 +1,17 @@
# VHS tape for lazygitclj - Standard 16:9 aspect ratio (1280x720)
Output lazygitclj-standard-16-9.gif
Require bb
Set Shell "bash"
Set Width 1280
Set Height 720
Set FontSize 14
Set Framerate 10
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-ar && cd /tmp/lazygitclj-e2e-ar && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 3s
Screenshot lazygitclj-standard-16-9.png
Type "q"
Sleep 500ms
@@ -0,0 +1,17 @@
# VHS tape for lazygitclj - Tall 9:16 aspect ratio (600x1000)
Output lazygitclj-tall-9-16.gif
Require bb
Set Shell "bash"
Set Width 600
Set Height 1000
Set FontSize 14
Set Framerate 10
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-ar && cd /tmp/lazygitclj-e2e-ar && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 3s
Screenshot lazygitclj-tall-9-16.png
Type "q"
Sleep 500ms
@@ -0,0 +1,17 @@
# VHS tape for lazygitclj - Ultrawide 32:9 aspect ratio (1600x450)
Output lazygitclj-ultrawide-32-9.gif
Require bb
Set Shell "bash"
Set Width 1600
Set Height 450
Set FontSize 14
Set Framerate 10
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-ar && cd /tmp/lazygitclj-e2e-ar && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 3s
Screenshot lazygitclj-ultrawide-32-9.png
Type "q"
Sleep 500ms
@@ -0,0 +1,17 @@
# VHS tape for lazygitclj - Wide 21:9 aspect ratio (1400x600)
Output lazygitclj-wide-21-9.gif
Require bb
Set Shell "bash"
Set Width 1400
Set Height 600
Set FontSize 14
Set Framerate 10
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-ar && cd /tmp/lazygitclj-e2e-ar && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 3s
Screenshot lazygitclj-wide-21-9.png
Type "q"
Sleep 500ms
+68
View File
@@ -0,0 +1,68 @@
# VHS E2E test for lazygitclj - Branch operations
# Tests branch create, delete, rename, merge, checkout
Output test/e2e/output/branch-operations.gif
Output test/e2e/output/branch-operations.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo and run lazygitclj
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branch-ops && cd /tmp/lazygitclj-e2e-branch-ops && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# First, clean up working tree by stashing
Type "s"
Sleep 500ms
# Switch to branches panel
Type "3"
Sleep 500ms
# Create new branch with 'n'
Type "n"
Sleep 500ms
Set TypingSpeed 50ms
Type "test-branch"
Enter
Sleep 1s
# Should have created and switched to test-branch
# Go back to main - select main first
Type "j"
Sleep 300ms
Type "j"
Sleep 300ms
# Enter to checkout main
Enter
Sleep 1s
# Navigate to test-branch and delete it with 'd'
Type "k"
Sleep 300ms
Type "d"
Sleep 1s
# Branch should be deleted
# Merge feature-branch into main
# Navigate to feature-branch
Type "k"
Sleep 300ms
# Merge with 'M'
Type "M"
Sleep 1s
# Should show merge message
# Quit
Type "q"
Sleep 1s
+48
View File
@@ -0,0 +1,48 @@
# VHS E2E test for lazygitclj - Branches panel tabs
# Tests tab switching between Local, Remotes, and Tags ([ and ] keys)
Output test/e2e/output/branches-tabs.gif
Output test/e2e/output/branches-tabs.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo with tags and run lazygitclj
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-branches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Switch to branches panel with '3'
Type "3"
Sleep 500ms
# Should show [Local] | Remotes | Tags tab indicator
# Switch to Remotes tab with ']'
Type "]"
Sleep 500ms
# Should show Local | [Remotes] | Tags
# Note: No remote branches in test repo, so empty
# Switch to Tags with ']' again
Type "]"
Sleep 500ms
# Should show Local | Remotes | [Tags] with v1.0.0 tag
Type "j"
Sleep 500ms
# Switch back to Local with '['
Type "["
Sleep 500ms
Type "["
Sleep 500ms
# Should be back on Local tab
# Quit
Type "q"
Sleep 1s
+39
View File
@@ -0,0 +1,39 @@
# VHS E2E test for lazygitclj - Branch operations
# Tests branch checkout (like lazygit's Enter key on branches)
Output test/e2e/output/branches.gif
Output test/e2e/output/branches.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo (clean working tree) and run lazygitclj
Type "cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-branch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Switch to Branches panel with '3' (like lazygit number shortcuts)
Type "3"
Sleep 500ms
# Navigate to feature-branch
Type "j"
Sleep 500ms
# Checkout branch with Enter
Enter
Sleep 1s
# Should now be on feature-branch (shown in status bar)
# Switch back to Files panel to verify
Type "1"
Sleep 500ms
# Quit
Type "q"
Sleep 1s
+85
View File
@@ -0,0 +1,85 @@
# VHS E2E test for lazygitclj - Commit workflow with git verification
# Tests the full commit workflow: stage files, open commit modal, type message, commit
# Verifies the commit was actually made using git CLI
Output test/e2e/output/commit-verify.gif
Output test/e2e/output/commit-verify.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup: Create a fresh test repo
Type "rm -rf /tmp/lazygitclj-e2e-commit-verify && mkdir -p /tmp/lazygitclj-e2e-commit-verify && cd /tmp/lazygitclj-e2e-commit-verify"
Enter
Sleep 500ms
# Initialize git repo
Type "git init -b main && git config user.email 'test@example.com' && git config user.name 'Test User'"
Enter
Sleep 500ms
# Create initial commit
Type "echo 'initial' > README.md && git add . && git commit -m 'Initial commit'"
Enter
Sleep 500ms
# Create a new file to commit with lazygitclj
Type "echo 'test content for commit' > test-file.txt"
Enter
Sleep 500ms
# Show the file exists
Type "ls -la"
Enter
Sleep 500ms
# Run lazygitclj
Type "bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Stage the file with 'a' (stage all)
Type "a"
Sleep 500ms
# Press 'c' to open commit modal
Type "c"
Sleep 1s
# Type commit message
Set TypingSpeed 50ms
Type "Add test file via lazygitclj"
Sleep 500ms
# Press Enter to commit
Enter
Sleep 1s
# Should see "Committed!" message
# Navigate to commits panel to see the new commit
Type "4"
Sleep 500ms
# Quit lazygitclj
Type "q"
Sleep 1s
# Verify the commit was made using git CLI
Type "git log --oneline -2"
Enter
Sleep 500ms
# Show that the file is tracked
Type "git status"
Enter
Sleep 500ms
# Show commit details
Type "git show --stat HEAD"
Enter
Sleep 1s
+50
View File
@@ -0,0 +1,50 @@
# VHS E2E test for lazygitclj - Commit workflow
# Tests committing staged files (like lazygit's c key)
Output test/e2e/output/commit.gif
Output test/e2e/output/commit.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo and run lazygitclj
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Stage all files first
Type "a"
Sleep 500ms
# Press 'c' to start commit (like lazygit)
Type "c"
Sleep 500ms
# Type commit message (with typing speed set)
Set TypingSpeed 50ms
Type "Add test changes"
Sleep 500ms
# Press Enter to commit
Enter
Sleep 1s
# Should see "Committed!" message and files should be cleared
# Check commits panel to verify
Type "2"
Sleep 500ms
# Navigate commits to see new commit
Type "j"
Sleep 500ms
Type "k"
Sleep 500ms
# Quit
Type "q"
Sleep 1s
+46
View File
@@ -0,0 +1,46 @@
# VHS E2E test for lazygitclj - Commits panel tabs
# Tests tab switching between Commits and Reflog ([ and ] keys)
Output test/e2e/output/commits-tabs.gif
Output test/e2e/output/commits-tabs.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo and run lazygitclj
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-commits-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Switch to commits panel with '2'
Type "2"
Sleep 500ms
# Should show [Commits] | Reflog tab indicator
# Switch to Reflog tab with ']'
Type "]"
Sleep 500ms
# Should now show Commits | [Reflog] tab indicator
# Navigate reflog entries
Type "j"
Sleep 500ms
Type "k"
Sleep 500ms
# Switch back to Commits with '['
Type "["
Sleep 500ms
# Should be back on Commits tab
Type "j"
Sleep 500ms
# Quit
Type "q"
Sleep 1s
+31
View File
@@ -0,0 +1,31 @@
# VHS debug test - check git status and lazygitclj display
Output test/e2e/output/debug.gif
Output test/e2e/output/debug.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-debug"
Enter
Sleep 2s
# Show git status
Type "cd /tmp/lazygitclj-debug && git status --porcelain"
Enter
Sleep 1s
# Run lazygitclj
Type "bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Quit
Type "q"
Sleep 1s
View File
+50
View File
@@ -0,0 +1,50 @@
# VHS E2E test for lazygitclj - Help panel
# Tests help display (? key shows keybindings)
Output test/e2e/output/help-panel.gif
Output test/e2e/output/help-panel.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo and run lazygitclj
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Open help panel with '?'
Type "?"
Sleep 2s
# Help panel should show keybindings for:
# - Global keys (q, r, tab, 1-4, j/k, z/Z, p, P, ?, D)
# - Files panel (space, a, c, d, s, S)
# - Commits panel ([/], space, g, C, t, r, y)
# - Branches panel ([/], enter, n, d, R, M, f)
# - Stash panel (space, g, d, n)
# Close help with escape
Escape
Sleep 500ms
# Verify we're back to normal view
Type "j"
Sleep 500ms
# Open help again with '?'
Type "?"
Sleep 1s
# Close with 'q' (also works)
Type "q"
Sleep 500ms
# Should be back to normal view
# Quit the app
Type "q"
Sleep 1s
+73
View File
@@ -0,0 +1,73 @@
# VHS E2E test for lazygitclj - Basic navigation
# Tests panel switching and cursor movement (like lazygit)
Output test/e2e/output/navigation.gif
Output test/e2e/output/navigation.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo and run lazygitclj
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-nav && cd /tmp/lazygitclj-e2e-nav && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Should start on Files panel (panel 1)
# Navigate down with j (vim style like lazygit)
Type "j"
Sleep 500ms
Type "j"
Sleep 500ms
# Navigate up with k
Type "k"
Sleep 500ms
# Switch to Commits panel with Tab
Tab
Sleep 500ms
# Navigate commits
Type "j"
Sleep 500ms
Type "k"
Sleep 500ms
# Switch to Branches panel with Tab
Tab
Sleep 500ms
# Navigate branches
Type "j"
Sleep 500ms
# Switch back to Files using number key (like lazygit)
Type "1"
Sleep 500ms
# Switch to Commits with number key
Type "2"
Sleep 500ms
# Switch to Branches with number key
Type "3"
Sleep 500ms
# Test arrow keys too
Up
Sleep 300ms
Down
Sleep 300ms
# Refresh with r
Type "r"
Sleep 500ms
# Quit with q
Type "q"
Sleep 1s
+57
View File
@@ -0,0 +1,57 @@
# VHS E2E test for lazygitclj - Reset options menu
# Tests reset options (D key opens reset menu)
Output test/e2e/output/reset-menu.gif
Output test/e2e/output/reset-menu.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo with changes and run lazygitclj
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Open reset options menu with 'D'
Type "D"
Sleep 1s
# Menu should show:
# s - Soft reset (uncommit, keep staged)
# m - Mixed reset (uncommit, unstage)
# h - Hard reset (discard all)
# u - Unstage all staged changes
# d - Discard all unstaged changes
# c - Clean untracked files
# Cancel with escape
Escape
Sleep 500ms
# Open menu again and unstage all with 'u'
Type "D"
Sleep 500ms
Type "u"
Sleep 1s
# All staged files should now be unstaged
# Verify in files panel
Type "j"
Sleep 500ms
# Open reset menu again to discard unstaged
Type "D"
Sleep 500ms
Type "d"
Sleep 1s
# Modified files should be clean now (only untracked remain)
# Quit
Type "q"
Sleep 1s
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# Run all VHS e2e tests
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
echo "Running lazygitclj VHS e2e tests..."
echo "================================="
# List of test tapes
TESTS=(
"debug.tape"
"navigation.tape"
"staging.tape"
"commit.tape"
"commit-verify.tape"
"branches.tape"
"branch-operations.tape"
"stash-operations.tape"
"stash-menu.tape"
"help-panel.tape"
"reset-menu.tape"
"commits-tabs.tape"
"branches-tabs.tape"
"undo-redo.tape"
)
PASSED=0
FAILED=0
for tape in "${TESTS[@]}"; do
echo ""
echo "Running: $tape"
echo "---"
if timeout 60 vhs "$tape" 2>&1; then
echo "PASSED: $tape"
((PASSED++))
else
echo "FAILED: $tape"
((FAILED++))
fi
done
echo ""
echo "================================="
echo "Results: $PASSED passed, $FAILED failed"
echo "================================="
if [ $FAILED -gt 0 ]; then
exit 1
fi
+47
View File
@@ -0,0 +1,47 @@
#!/bin/bash
# Setup a test git repository for e2e testing
set -e
TEST_REPO="${1:-/tmp/lazygitclj-test-repo}"
# Clean up if exists
rm -rf "$TEST_REPO"
mkdir -p "$TEST_REPO"
cd "$TEST_REPO"
# Initialize git repo
git init -b main
git config user.email "test@example.com"
git config user.name "Test User"
# Create initial files and commit
echo "# Test Project" > README.md
echo "line1" > file1.txt
echo "line1" > file2.txt
git add .
git commit -m "Initial commit"
# Create a feature branch
git checkout -b feature-branch
echo "feature work" >> file1.txt
git add .
git commit -m "Feature work"
git checkout main
# Create some unstaged changes
echo "modified" >> file1.txt
# Create untracked file
echo "new file content" > newfile.txt
# Stage one file
echo "staged content" >> file2.txt
git add file2.txt
echo "Test repo created at $TEST_REPO"
echo " - main branch with 1 commit"
echo " - feature-branch with 1 extra commit"
echo " - 1 staged file (file2.txt)"
echo " - 1 unstaged file (file1.txt)"
echo " - 1 untracked file (newfile.txt)"
+45
View File
@@ -0,0 +1,45 @@
# VHS E2E test for lazygitclj - File staging/unstaging
# Tests staging and unstaging files (like lazygit's space key)
Output test/e2e/output/staging.gif
Output test/e2e/output/staging.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo and run lazygitclj
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Should be on Files panel with some staged and unstaged files
# Move to the staged file and unstage it with space
Type " "
Sleep 500ms
# Move down to an unstaged file
Type "j"
Sleep 500ms
# Stage it with space
Type " "
Sleep 500ms
# Stage all with 'a' (like lazygit)
Type "a"
Sleep 500ms
# Move to a file and discard changes with 'd'
Type "j"
Sleep 500ms
Type "d"
Sleep 500ms
# Quit
Type "q"
Sleep 1s
+50
View File
@@ -0,0 +1,50 @@
# VHS E2E test for lazygitclj - Stash menu options
# Tests stash options menu (S key opens menu)
Output test/e2e/output/stash-menu.gif
Output test/e2e/output/stash-menu.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo with changes and run lazygitclj
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Open stash options menu with 'S'
Type "S"
Sleep 1s
# Menu should be visible showing options:
# a - Stash all changes
# i - Stash all and keep index
# U - Stash including untracked
# s - Stash staged only
# u - Stash unstaged only
# Cancel with escape first
Escape
Sleep 500ms
# Open menu again
Type "S"
Sleep 500ms
# Stash all with 'a'
Type "a"
Sleep 1s
# Files should be clean now
# Check stash panel
Type "4"
Sleep 500ms
# Quit
Type "q"
Sleep 1s
+52
View File
@@ -0,0 +1,52 @@
# VHS E2E test for lazygitclj - Stash operations
# Tests stash functionality (s for quick stash, S for stash menu)
Output test/e2e/output/stash-operations.gif
Output test/e2e/output/stash-operations.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo with changes and run lazygitclj
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Should be on Files panel with staged and unstaged files
# Quick stash all changes with 's'
Type "s"
Sleep 1s
# Files panel should now be clean, check stash panel
Type "4"
Sleep 500ms
# Should see one stash entry
# Navigate to stash and apply it with space
Type " "
Sleep 1s
# Go back to files panel - should see changes restored
Type "1"
Sleep 500ms
# Stash again with 's'
Type "s"
Sleep 1s
# Go to stash panel
Type "4"
Sleep 500ms
# Pop the stash with 'g' (apply and remove)
Type "g"
Sleep 1s
# Quit
Type "q"
Sleep 1s
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,816 @@
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]]
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]]
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]]j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]]j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]]j[
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]]j[
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]]j[[
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]]j[[
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]]j[[q
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-branches-tabs && cd /tmp/lazygitclj-e2e-bra
nches-tabs && git tag v1.0.0 && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 3]]j[[q
────────────────────────────────────────────────────────────────────────────────
+696
View File
@@ -0,0 +1,696 @@
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
>
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
>
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
> 3
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
> 3
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
> 3j
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
> 3j
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
> 3j
bash: 3j: command not found
>
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
> 3j
bash: 3j: command not found
>
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
> 3j
bash: 3j: command not found
> 1
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
> 3j
bash: 3j: command not found
> 1
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
> 3j
bash: 3j: command not found
> 1q
────────────────────────────────────────────────────────────────────────────────
> cd /tmp && rm -rf lazygitclj-e2e-branch && git clone /tmp/lazygitclj-e2e-nav lazygitclj-e2e-b
ranch 2>/dev/null && cd lazygitclj-e2e-branch && git checkout main && bb --config /home/ajet/re
pos/lazygitclj/bb.edn start
> 3j
bash: 3j: command not found
> 1q
────────────────────────────────────────────────────────────────────────────────
File diff suppressed because it is too large Load Diff
+906
View File
@@ -0,0 +1,906 @@
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> a
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> a
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ac
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ac
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ac
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
bash: acAdd: command not found
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
bash: acAdd: command not found
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
bash: acAdd: command not found
> 2
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
bash: acAdd: command not found
> 2
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
bash: acAdd: command not found
> 2j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
bash: acAdd: command not found
> 2j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
bash: acAdd: command not found
> 2jk
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
bash: acAdd: command not found
> 2jk
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
bash: acAdd: command not found
> 2jkq
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commit && cd /tmp/lazygitclj-e2e-commit &&
bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> acAdd test changes
bash: acAdd: command not found
> 2jkq
────────────────────────────────────────────────────────────────────────────────
+816
View File
@@ -0,0 +1,816 @@
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]jk
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]jk
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]jk[
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]jk[
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]jk[j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]jk[j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]jk[jq
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-commits-tabs && cd /tmp/lazygitclj-e2e-comm
its-tabs && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> 2]jk[jq
────────────────────────────────────────────────────────────────────────────────
+636
View File
@@ -0,0 +1,636 @@
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-debug
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-debug
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-debug
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-debug
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> cd /tmp/lazygitclj-debug && git status --porcelain
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-debug
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> cd /tmp/lazygitclj-debug && git status --porcelain
bash: cd: /tmp/lazygitclj-debug: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-debug
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> cd /tmp/lazygitclj-debug && git status --porcelain
bash: cd: /tmp/lazygitclj-debug: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-debug
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> cd /tmp/lazygitclj-debug && git status --porcelain
bash: cd: /tmp/lazygitclj-debug: No such file or directory
> bb --config /home/ajet/repos/lazygitclj/bb.edn start
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-debug
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> cd /tmp/lazygitclj-debug && git status --porcelain
bash: cd: /tmp/lazygitclj-debug: No such file or directory
> bb --config /home/ajet/repos/lazygitclj/bb.edn start
Starting lazygitclj...
────────────────────────────────────────────────────────────────────────────────
┌─ 1 Status ─────────────────┐ ┌─ 0 Main ─────────────────────────────────────────────────────┐
│ master → 8b44d40 │ │ │
└────────────────────────────┘ │ │
╔═ 2 Files (44) ═════════════╗ │ │
║ M .gitignore ║ │ │
║ A CLAUDE.md ║ │ │
║ A PRD.md ║ │ │
║ M bb.edn ║ │ │
╚════════════════════════════╝ │ │
┌─ 3 Branches [L] R T ───────┐ │ │
│ * master │ │ │
│ new-feature │ │ │
│ │ │ │
│ │ │ │
└────────────────────────────┘ │ │
┌─ 4 Commits [C] R ──────────┐ │ │
│ 8b44d40 Fix syntax err... │ │ │
│ 5a8629b Initial clajyg... │ │ │
│ │ │ │
│ │ │ │
└────────────────────────────┘ │ │
┌─ 5 Stash (0) ──────────────┐ │ │
│ No stashes │ │ │
│ │ └──────────────────────────────────────────────────────────────┘
│ │ ┌─ Command Log ────────────────────────────────────────────────┐
│ │ │ │
└────────────────────────────┘ │ │
└──────────────────────────────────────────────────────────────┘
q:quit ?:help h/l:panels j/k:nav spc:stage a:all c:commit p/P:pull/push
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-debug
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> cd /tmp/lazygitclj-debug && git status --porcelain
bash: cd: /tmp/lazygitclj-debug: No such file or directory
> bb --config /home/ajet/repos/lazygitclj/bb.edn start
Starting lazygitclj...
Goodbye!
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-debug
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> cd /tmp/lazygitclj-debug && git status --porcelain
bash: cd: /tmp/lazygitclj-debug: No such file or directory
> bb --config /home/ajet/repos/lazygitclj/bb.edn start
Starting lazygitclj...
Goodbye!
>
────────────────────────────────────────────────────────────────────────────────
+756
View File
@@ -0,0 +1,756 @@
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ?
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ?
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ?
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ?
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ?
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ?
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ??
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ??
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ??q
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ??q
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ??qq
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-help && cd /tmp/lazygitclj-e2e-help && bb -
-config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> ??qq
────────────────────────────────────────────────────────────────────────────────
File diff suppressed because it is too large Load Diff
+876
View File
@@ -0,0 +1,876 @@
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> D
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> D
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> D
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> D
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> D
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> D
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> Du
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> Du
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> Duj
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> Duj
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> DujD
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> DujD
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> DujDd
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> DujDd
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> DujDdq
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-reset && cd /tmp/lazygitclj-e2e-reset && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> DujDdq
────────────────────────────────────────────────────────────────────────────────
+816
View File
@@ -0,0 +1,816 @@
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j a
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j a
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j aj
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j aj
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j ajd
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j ajd
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j ajdq
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stage && cd /tmp/lazygitclj-e2e-stage && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> j ajdq
────────────────────────────────────────────────────────────────────────────────
+756
View File
@@ -0,0 +1,756 @@
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> S
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> S
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> S
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> S
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> S
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> S
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> Sa
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> Sa
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> Sa4
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> Sa4
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> Sa4q
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash-menu && cd /tmp/lazygitclj-e2e-stash-
menu && bb --config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> Sa4q
────────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,876 @@
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
>
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4 1
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4 1
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4 1s
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4 1s
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4 1s4
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4 1s4
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4 1s4g
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4 1s4g
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4 1s4gq
────────────────────────────────────────────────────────────────────────────────
> ./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-stash && cd /tmp/lazygitclj-e2e-stash && bb
--config /home/ajet/repos/lazygitclj/bb.edn start
bash: ./test/e2e/setup-test-repo.sh: No such file or directory
> s4 1s4gq
────────────────────────────────────────────────────────────────────────────────
File diff suppressed because it is too large Load Diff
+59
View File
@@ -0,0 +1,59 @@
# VHS E2E test for lazygitclj - Undo/Redo operations
# Tests undo (z) and redo (Z) via reflog navigation
Output test/e2e/output/undo-redo.gif
Output test/e2e/output/undo-redo.ascii
Require bb
Set Shell "bash"
Set FontSize 14
Set Width 1000
Set Height 600
Set Framerate 10
# Setup test repo and make some changes
Type "./test/e2e/setup-test-repo.sh /tmp/lazygitclj-e2e-undo && cd /tmp/lazygitclj-e2e-undo && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Stage all and commit
Type "a"
Sleep 500ms
Type "c"
Sleep 500ms
Set TypingSpeed 50ms
Type "First commit message"
Enter
Sleep 1s
# Create another change
# Exit and modify file
Type "q"
Sleep 500ms
Type "echo 'more changes' >> file1.txt && bb --config /home/ajet/repos/lazygitclj/bb.edn start"
Enter
Sleep 2s
# Stage and commit again
Type "a"
Sleep 500ms
Type "c"
Sleep 500ms
Type "Second commit"
Enter
Sleep 1s
# Now undo with 'z' - should reset to previous state
Type "z"
Sleep 1s
# Status bar should show undo message
# Redo with 'Z'
Type "Z"
Sleep 1s
# Should be back to latest state
# Quit
Type "q"
Sleep 1s
+288
View File
@@ -0,0 +1,288 @@
(ns lazygitclj.core-test
"Unit tests for lazygitclj.core namespace - model and update functions"
(:require [clojure.test :refer [deftest testing is]]
[lazygitclj.core :as core]
[tui.simple :as tui]))
;; Helper to create key messages in the format the TUI library uses
(defn key-msg [k]
(cond
(char? k) [:key {:char k}]
(keyword? k) [:key k]
(and (vector? k) (= :ctrl (first k))) [:key {:ctrl true :char (second k)}]
:else [:key k]))
;; === Model Tests ===
(deftest test-file-items
(testing "combines staged and unstaged files"
(let [model {:staged [{:path "a.txt"} {:path "b.txt"}]
:unstaged [{:path "c.txt"}]}
items (core/file-items model)]
(is (= 3 (count items)))
(is (= :staged (:type (first items))))
(is (= :unstaged (:type (last items))))))
(testing "returns empty vector when no files"
(let [model {:staged [] :unstaged []}]
(is (= [] (core/file-items model))))))
(deftest test-current-items
(testing "returns file items for files panel"
(let [model {:panel :files
:staged [{:path "a.txt"}]
:unstaged [{:path "b.txt"}]}]
(is (= 2 (count (core/current-items model))))))
(testing "returns commits for commits panel"
(let [model {:panel :commits
:commits-tab :commits
:commits [{:sha "abc123"}]
:reflog []}]
(is (= 1 (count (core/current-items model))))))
(testing "returns reflog for commits panel with reflog tab"
(let [model {:panel :commits
:commits-tab :reflog
:commits []
:reflog [{:sha "def456"} {:sha "ghi789"}]}]
(is (= 2 (count (core/current-items model))))))
(testing "returns branches for branches panel"
(let [model {:panel :branches
:branches-tab :local
:branches ["main" "feature"]}]
(is (= 2 (count (core/current-items model))))))
(testing "returns remote branches for branches panel with remotes tab"
(let [model {:panel :branches
:branches-tab :remotes
:remote-branches ["origin/main"]}]
(is (= 1 (count (core/current-items model))))))
(testing "returns tags for branches panel with tags tab"
(let [model {:panel :branches
:branches-tab :tags
:tags ["v1.0.0" "v2.0.0"]}]
(is (= 2 (count (core/current-items model))))))
(testing "returns stashes for stash panel"
(let [model {:panel :stash
:stashes [{:ref "stash@{0}"}]}]
(is (= 1 (count (core/current-items model)))))))
(deftest test-clamp-cursor
(testing "clamps cursor to valid range"
(let [model {:panel :files
:cursor 10
:staged [{:path "a.txt"}]
:unstaged []}
clamped (core/clamp-cursor model)]
(is (= 0 (:cursor clamped)))))
(testing "keeps cursor at valid position"
(let [model {:panel :files
:cursor 1
:staged [{:path "a.txt"} {:path "b.txt"}]
:unstaged []}
clamped (core/clamp-cursor model)]
(is (= 1 (:cursor clamped)))))
(testing "handles empty lists"
(let [model {:panel :files
:cursor 5
:staged []
:unstaged []}
clamped (core/clamp-cursor model)]
(is (= 0 (:cursor clamped))))))
(deftest test-truncate
(testing "truncates long strings"
(is (= "hello..." (core/truncate "hello world" 5))))
(testing "keeps short strings"
(is (= "hi" (core/truncate "hi" 10))))
(testing "handles nil"
(is (nil? (core/truncate nil 10)))))
;; === Update Tests ===
(deftest test-update-model-quit
(testing "q returns quit command"
(let [[model cmd] (core/update-model {} (key-msg \q))]
(is (= [:quit] cmd))))
(testing "ctrl-c returns quit command"
(let [[model cmd] (core/update-model {} [:key {:ctrl true :char \c}])]
(is (= [:quit] cmd)))))
(deftest test-update-model-panel-switch
(testing "number keys switch panels (2-5 matching lazygit)"
(let [[model _] (core/update-model {:panel :commits :cursor 0
:staged [] :unstaged []} (key-msg \2))]
(is (= :files (:panel model))))
(let [[model _] (core/update-model {:panel :files :cursor 0
:branches-tab :local
:branches [] :remote-branches []
:tags []} (key-msg \3))]
(is (= :branches (:panel model))))
(let [[model _] (core/update-model {:panel :files :cursor 0
:commits-tab :commits
:commits [] :reflog []} (key-msg \4))]
(is (= :commits (:panel model))))
(let [[model _] (core/update-model {:panel :files :cursor 0
:stashes []} (key-msg \5))]
(is (= :stash (:panel model)))))
(testing "l key cycles panels right (files → branches → commits → stash → files)"
(let [[model _] (core/update-model {:panel :files :cursor 0
:branches-tab :local
:branches [] :remote-branches []
:tags []} (key-msg \l))]
(is (= :branches (:panel model))))
(let [[model _] (core/update-model {:panel :branches :cursor 0
:commits-tab :commits
:commits [] :reflog []} (key-msg \l))]
(is (= :commits (:panel model))))
(let [[model _] (core/update-model {:panel :commits :cursor 0
:stashes []} (key-msg \l))]
(is (= :stash (:panel model))))
(let [[model _] (core/update-model {:panel :stash :cursor 0
:staged [] :unstaged []} (key-msg \l))]
(is (= :files (:panel model))))))
(deftest test-update-model-cursor-movement
(testing "j moves cursor down"
(let [[model _] (core/update-model {:panel :files :cursor 0
:staged [{:path "a"} {:path "b"}]
:unstaged []} (key-msg \j))]
(is (= 1 (:cursor model)))))
(testing "k moves cursor up"
(let [[model _] (core/update-model {:panel :files :cursor 1
:staged [{:path "a"} {:path "b"}]
:unstaged []} (key-msg \k))]
(is (= 0 (:cursor model)))))
(testing "down arrow moves cursor down"
(let [[model _] (core/update-model {:panel :files :cursor 0
:staged [{:path "a"} {:path "b"}]
:unstaged []} (key-msg :down))]
(is (= 1 (:cursor model)))))
(testing "up arrow moves cursor up"
(let [[model _] (core/update-model {:panel :files :cursor 1
:staged [{:path "a"} {:path "b"}]
:unstaged []} (key-msg :up))]
(is (= 0 (:cursor model))))))
(deftest test-update-model-help-menu
(testing "? opens help menu"
(let [[model _] (core/update-model {:menu-mode nil} (key-msg \?))]
(is (= :help (:menu-mode model))))))
(deftest test-update-model-reset-menu
(testing "D opens reset options menu"
(let [[model _] (core/update-model {:menu-mode nil} (key-msg \D))]
(is (= :reset-options (:menu-mode model))))))
(deftest test-update-model-input-mode
(testing "c in files panel opens commit input when staged files exist"
(let [[model _] (core/update-model {:panel :files
:staged [{:path "a.txt"}]
:unstaged []} (key-msg \c))]
(is (= :commit (:input-mode model)))))
(testing "c in files panel shows message when no staged files"
(let [[model _] (core/update-model {:panel :files
:staged []
:unstaged []} (key-msg \c))]
(is (= "Nothing staged to commit" (:message model))))))
(deftest test-update-input-mode
(testing "escape cancels input mode"
(let [[model _] (core/update-input-mode {:input-mode :commit
:input-buffer "test"
:input-context nil} (key-msg :escape))]
(is (nil? (:input-mode model)))
(is (= "" (:input-buffer model)))))
(testing "backspace removes last character"
(let [[model _] (core/update-input-mode {:input-mode :commit
:input-buffer "abc"
:input-context nil} (key-msg :backspace))]
(is (= "ab" (:input-buffer model))))))
(deftest test-update-commits-tabs
(testing "] switches to reflog tab in commits panel"
(let [[model _] (core/update-model {:panel :commits
:commits-tab :commits
:cursor 0
:commits []
:reflog []} (key-msg \]))]
(is (= :reflog (:commits-tab model)))))
(testing "[ switches to commits tab in commits panel"
(let [[model _] (core/update-model {:panel :commits
:commits-tab :reflog
:cursor 0
:commits []
:reflog []} (key-msg \[))]
(is (= :commits (:commits-tab model))))))
(deftest test-update-branches-tabs
(testing "] cycles branches tabs forward"
(let [[model _] (core/update-branches {:branches-tab :local
:cursor 0
:branches []} (key-msg \]))]
(is (= :remotes (:branches-tab model))))
(let [[model _] (core/update-branches {:branches-tab :remotes
:cursor 0
:remote-branches []} (key-msg \]))]
(is (= :tags (:branches-tab model))))
(let [[model _] (core/update-branches {:branches-tab :tags
:cursor 0
:tags []} (key-msg \]))]
(is (= :local (:branches-tab model)))))
(testing "[ cycles branches tabs backward"
(let [[model _] (core/update-branches {:branches-tab :local
:cursor 0
:tags []} (key-msg \[))]
(is (= :tags (:branches-tab model))))))
(deftest test-update-stash-menu
(testing "escape closes stash menu"
(let [[model _] (core/update-stash-menu {:menu-mode :stash-options} (key-msg :escape))]
(is (nil? (:menu-mode model))))))
(deftest test-update-reset-menu
(testing "escape closes reset menu"
(let [[model _] (core/update-reset-menu {:menu-mode :reset-options} (key-msg :escape))]
(is (nil? (:menu-mode model))))))
(deftest test-update-help
(testing "escape closes help"
(let [[model _] (core/update-help {:menu-mode :help} (key-msg :escape))]
(is (nil? (:menu-mode model)))))
(testing "q closes help"
(let [[model _] (core/update-help {:menu-mode :help} (key-msg \q))]
(is (nil? (:menu-mode model)))))
(testing "? closes help"
(let [[model _] (core/update-help {:menu-mode :help} (key-msg \?))]
(is (nil? (:menu-mode model))))))
;; Run tests when executed directly
(defn -main [& args]
(clojure.test/run-tests 'lazygitclj.core-test))
+323
View File
@@ -0,0 +1,323 @@
(ns lazygitclj.git-test
"Unit tests for lazygitclj.git namespace"
(:require [clojure.test :refer [deftest testing is use-fixtures]]
[lazygitclj.git :as git]
[clojure.string :as str]
[babashka.process :refer [shell]]))
;; === Test Fixtures ===
(def ^:dynamic *test-repo* nil)
(defn- sh [& args]
(try
(-> (apply shell {:out :string :err :string :dir *test-repo*} args)
:out
str/trim-newline)
(catch Exception _ nil)))
(defn create-test-repo []
(let [repo-dir (str "/tmp/lazygitclj-unit-test-" (System/currentTimeMillis))]
(shell "mkdir" "-p" repo-dir)
(shell {:dir repo-dir} "git" "init" "-b" "main")
(shell {:dir repo-dir} "git" "config" "user.email" "test@example.com")
(shell {:dir repo-dir} "git" "config" "user.name" "Test User")
;; Create initial files
(spit (str repo-dir "/README.md") "# Test Project\n")
(spit (str repo-dir "/file1.txt") "line1\n")
(shell {:dir repo-dir} "git" "add" ".")
(shell {:dir repo-dir} "git" "commit" "-m" "Initial commit")
repo-dir))
(defn delete-test-repo [repo-dir]
(shell "rm" "-rf" repo-dir))
(defn with-test-repo [f]
(let [repo-dir (create-test-repo)]
(try
(binding [*test-repo* repo-dir]
;; Change to test repo for git commands
(let [original-dir (System/getProperty "user.dir")]
(System/setProperty "user.dir" repo-dir)
(try
(f)
(finally
(System/setProperty "user.dir" original-dir)))))
(finally
(delete-test-repo repo-dir)))))
(use-fixtures :each with-test-repo)
;; === Status Tests ===
(deftest test-current-branch
(testing "returns current branch name"
(is (= "main" (sh "git" "branch" "--show-current")))))
(deftest test-head-sha
(testing "returns short SHA of HEAD"
(let [sha (sh "git" "rev-parse" "--short" "HEAD")]
(is (string? sha))
(is (>= (count sha) 7)))))
(deftest test-repo-root
(testing "returns repo root directory"
(let [root (sh "git" "rev-parse" "--show-toplevel")]
(is (string? root))
(is (str/starts-with? root "/tmp/lazygitclj-unit-test-")))))
;; === File Status Tests ===
(deftest test-staged-files
(testing "returns empty list when nothing staged"
;; Modify file but don't stage
(spit (str *test-repo* "/file1.txt") "modified\n")
(let [staged (git/staged-files)]
(is (empty? staged))))
(testing "returns staged files after git add"
(spit (str *test-repo* "/file1.txt") "modified\n")
(sh "git" "add" "file1.txt")
(let [staged (git/staged-files)]
(is (= 1 (count staged)))
(is (= "file1.txt" (:path (first staged)))))))
(deftest test-unstaged-files
(testing "returns modified unstaged files"
(spit (str *test-repo* "/file1.txt") "modified\n")
(let [unstaged (git/unstaged-files)]
(is (= 1 (count unstaged)))
(is (= "file1.txt" (:path (first unstaged))))))
(testing "returns untracked files"
(spit (str *test-repo* "/newfile.txt") "new content\n")
(let [unstaged (git/unstaged-files)]
(is (some #(= "newfile.txt" (:path %)) unstaged)))))
;; === Branch Tests ===
(deftest test-branches
(testing "returns list of local branches"
(let [branches (git/branches)]
(is (vector? branches))
(is (some #(= "main" %) branches))))
(testing "includes newly created branches"
(sh "git" "branch" "feature-branch")
(let [branches (git/branches)]
(is (some #(= "feature-branch" %) branches)))))
(deftest test-create-branch
(testing "creates a new branch and switches to it"
(git/create-branch "new-feature")
(is (= "new-feature" (sh "git" "branch" "--show-current")))))
(deftest test-checkout-branch
(testing "switches to existing branch"
(sh "git" "branch" "other-branch")
(git/checkout-branch "other-branch")
(is (= "other-branch" (sh "git" "branch" "--show-current")))))
(deftest test-delete-branch
(testing "deletes a branch"
(sh "git" "branch" "to-delete")
(git/delete-branch "to-delete")
(let [branches (git/branches)]
(is (not (some #(= "to-delete" %) branches))))))
(deftest test-rename-branch
(testing "renames a branch"
(sh "git" "branch" "old-name")
(git/rename-branch "old-name" "new-name")
(let [branches (git/branches)]
(is (some #(= "new-name" %) branches))
(is (not (some #(= "old-name" %) branches))))))
;; === Staging Tests ===
(deftest test-stage-file
(testing "stages a modified file"
(spit (str *test-repo* "/file1.txt") "modified\n")
(git/stage-file "file1.txt")
(let [staged (git/staged-files)]
(is (= 1 (count staged))))))
(deftest test-unstage-file
(testing "unstages a staged file"
(spit (str *test-repo* "/file1.txt") "modified\n")
(sh "git" "add" "file1.txt")
(git/unstage-file "file1.txt")
(let [staged (git/staged-files)]
(is (empty? staged)))))
(deftest test-stage-all
(testing "stages all changed files"
(spit (str *test-repo* "/file1.txt") "modified\n")
(spit (str *test-repo* "/newfile.txt") "new\n")
(git/stage-all)
(let [staged (git/staged-files)]
(is (= 2 (count staged))))))
;; === Commit Tests ===
(deftest test-commit
(testing "creates a commit with message"
(spit (str *test-repo* "/file1.txt") "modified\n")
(sh "git" "add" ".")
(git/commit "Test commit message")
(let [log (sh "git" "log" "--oneline" "-1")]
(is (str/includes? log "Test commit message")))))
(deftest test-recent-commits
(testing "returns recent commits"
(let [commits (git/recent-commits 5)]
(is (seq commits))
(is (= "Initial commit" (:subject (first commits)))))))
;; === Stash Tests ===
(deftest test-stash-all
(testing "stashes all changes"
(spit (str *test-repo* "/file1.txt") "modified\n")
(git/stash-all)
(let [stashes (git/stash-list)]
(is (= 1 (count stashes))))
;; Working tree should be clean
(let [unstaged (git/unstaged-files)]
(is (empty? unstaged)))))
(deftest test-stash-pop
(testing "pops stash and removes it from list"
(spit (str *test-repo* "/file1.txt") "modified\n")
(git/stash-all)
(git/stash-pop)
(let [stashes (git/stash-list)
unstaged (git/unstaged-files)]
(is (empty? stashes))
(is (= 1 (count unstaged))))))
(deftest test-stash-apply
(testing "applies stash but keeps it in list"
(spit (str *test-repo* "/file1.txt") "modified\n")
(git/stash-all)
(git/stash-apply)
(let [stashes (git/stash-list)
unstaged (git/unstaged-files)]
(is (= 1 (count stashes)))
(is (= 1 (count unstaged))))))
(deftest test-stash-drop
(testing "drops stash from list"
(spit (str *test-repo* "/file1.txt") "modified\n")
(git/stash-all)
(git/stash-drop)
(let [stashes (git/stash-list)]
(is (empty? stashes)))))
;; === Reset Tests ===
(deftest test-discard-file
(testing "discards unstaged changes to a file"
(spit (str *test-repo* "/file1.txt") "modified\n")
(git/discard-file "file1.txt")
(let [content (slurp (str *test-repo* "/file1.txt"))]
(is (= "line1\n" content)))))
(deftest test-discard-all-unstaged
(testing "discards all unstaged changes"
(spit (str *test-repo* "/file1.txt") "modified\n")
(git/discard-all-unstaged)
(let [unstaged (git/unstaged-files)]
(is (empty? unstaged)))))
(deftest test-reset-staged
(testing "unstages all staged files"
(spit (str *test-repo* "/file1.txt") "modified\n")
(sh "git" "add" ".")
(git/reset-staged)
(let [staged (git/staged-files)]
(is (empty? staged)))))
;; === Tag Tests ===
(deftest test-list-tags
(testing "returns empty list when no tags"
(let [tags (git/list-tags)]
(is (empty? tags))))
(testing "returns tags after creating one"
(sh "git" "tag" "v1.0.0")
(let [tags (git/list-tags)]
(is (some #(= "v1.0.0" %) tags)))))
(deftest test-create-tag
(testing "creates a tag"
(git/create-tag "v2.0.0")
(let [tags (git/list-tags)]
(is (some #(= "v2.0.0" %) tags)))))
(deftest test-delete-tag
(testing "deletes a tag"
(sh "git" "tag" "to-delete")
(git/delete-tag "to-delete")
(let [tags (git/list-tags)]
(is (not (some #(= "to-delete" %) tags))))))
;; === Reflog Tests ===
(deftest test-reflog
(testing "returns reflog entries"
(let [reflog (git/reflog 10)]
(is (seq reflog))
(is (every? :sha reflog))
(is (every? :ref reflog)))))
;; === Cherry-pick and Revert Tests ===
(deftest test-cherry-pick-commit
(testing "cherry-picks a commit"
;; Create a branch with a commit
(sh "git" "checkout" "-b" "feature")
(spit (str *test-repo* "/feature.txt") "feature content\n")
(sh "git" "add" ".")
(sh "git" "commit" "-m" "Feature commit")
(let [feature-sha (sh "git" "rev-parse" "--short" "HEAD")]
;; Go back to main
(sh "git" "checkout" "main")
;; Cherry-pick
(git/cherry-pick-commit feature-sha)
;; Verify file exists
(is (.exists (java.io.File. (str *test-repo* "/feature.txt")))))))
(deftest test-revert-commit
(testing "reverts a commit"
;; Make a change and commit
(spit (str *test-repo* "/file1.txt") "modified\n")
(sh "git" "add" ".")
(sh "git" "commit" "-m" "Change to revert")
(let [sha (sh "git" "rev-parse" "--short" "HEAD")]
;; Revert it
(git/revert-commit sha)
;; Verify content is back to original
(let [content (slurp (str *test-repo* "/file1.txt"))]
(is (= "line1\n" content))))))
;; === Merge Tests ===
(deftest test-merge-branch
(testing "merges a branch into current"
;; Create feature branch with changes
(sh "git" "checkout" "-b" "to-merge")
(spit (str *test-repo* "/merged.txt") "merged content\n")
(sh "git" "add" ".")
(sh "git" "commit" "-m" "Merge commit")
;; Go back to main
(sh "git" "checkout" "main")
;; Merge
(git/merge-branch "to-merge")
;; Verify file exists on main
(is (.exists (java.io.File. (str *test-repo* "/merged.txt"))))))
;; Run tests when executed directly
(defn -main [& args]
(clojure.test/run-tests 'lazygitclj.git-test))
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bb
;; Run all unit tests for lazygitclj
(require '[clojure.test :as t])
;; Add test path
(require '[babashka.classpath :refer [add-classpath]])
(add-classpath "test")
;; Load test namespaces
(require 'lazygitclj.core-test)
(println "")
(println "Running lazygitclj unit tests...")
(println "================================")
(let [summary (t/run-tests 'lazygitclj.core-test)]
(println "")
(println "================================")
(when (or (pos? (:fail summary))
(pos? (:error summary)))
(System/exit 1)))