Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f130cd7
feat: add tag management, create (t) and delete (ctrl+t) tags
alfatm Dec 17, 2025
3364f19
chore: update readme.md
alfatm Dec 17, 2025
8d368cc
chore: cargo fmt
alfatm Dec 17, 2025
cff2c2e
feat: pending windows for operations
alfatm Dec 17, 2025
ceb69b8
feat: add refresh button, also refactor to Rc
alfatm Dec 17, 2025
4e4320a
fix: prevent panics and fix search+filter index conflict in commit_list
alfatm Dec 18, 2025
8a48497
feat: display context-aware hotkey hints in footer
alfatm Dec 18, 2025
1215e7d
feat: delete branches and tags from refs panel with 'd' key
alfatm Dec 18, 2025
1722887
chore: add script to generate test 10k commits repository with branch…
alfatm Dec 18, 2025
5fd0c41
chore: format
alfatm Dec 18, 2025
831d72d
refactor: improve error handling and remove panics
alfatm Dec 18, 2025
e7005aa
chore: format
alfatm Dec 18, 2025
e42adf6
chore: update readme
alfatm Dec 18, 2025
2d07ec7
Merge branch 'lusingander:master' into master
alfatm Dec 18, 2025
59a1985
feat: implement full repository refresh
alfatm Dec 21, 2025
7ede8b7
fix: prevent potential panic on UTF-8 multi-byte characters in tag na…
alfatm Dec 21, 2025
f4f5fd2
fix: handle partial deletion failures and bounds checking
alfatm Dec 21, 2025
975c5b4
feat: add ref name validation before creating tags
alfatm Dec 21, 2025
eb1ea51
fix: handle edge cases in text wrapping for pending overlay
alfatm Dec 21, 2025
372f86b
fix: sort non-semver tags descending to match semver ordering
alfatm Dec 21, 2025
1116576
fix: format
alfatm Dec 21, 2025
42d5ae3
Merge branch 'lusingander:master' into master
alfatm Dec 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ The default key bindings can be overridden. Please refer to [default-keybind.tom
| ------------------------------ | ----------- | --------------------- |
| <kbd>Ctrl-c</kbd> <kbd>q</kbd> | Quit app | `force_quit` `quit` |
| <kbd>?</kbd> | Open help | `help_toggle` |
| <kbd>r</kbd> | Refresh | `refresh` |

#### Commit List

Expand All @@ -200,11 +201,14 @@ The default key bindings can be overridden. Please refer to [default-keybind.tom
| <kbd>Enter</kbd> | Show commit details<br>Apply search (if searching) | `confirm` |
| <kbd>Tab</kbd> | Open refs list | `ref_list_toggle` |
| <kbd>/</kbd> | Start search | `search` |
| <kbd>Esc</kbd> | Cancel search | `cancel` |
| <kbd>f</kbd> | Start filter | `filter` |
| <kbd>Esc</kbd> | Cancel search/filter | `cancel` |
| <kbd>n/N</kbd> | Go to next/previous search match | `go_to_next` `go_to_previous` |
| <kbd>Ctrl-g</kbd> | Toggle ignore case (if searching) | `ignore_case_toggle` |
| <kbd>Ctrl-x</kbd> | Toggle fuzzy match (if searching) | `fuzzy_toggle` |
| <kbd>Alt-c</kbd> | Toggle ignore case (if searching/filtering) | `ignore_case_toggle` |
| <kbd>Ctrl-x</kbd> | Toggle fuzzy match (if searching/filtering) | `fuzzy_toggle` |
| <kbd>c/C</kbd> | Copy commit short/full hash | `short_copy` `full_copy` |
| <kbd>t</kbd> | Create tag on commit | `create_tag` |
| <kbd>Ctrl-t</kbd> | Delete tag from commit | `delete_tag` |
| <kbd>d</kbd> | Toggle custom user command view | `user_command_view_toggle_1` |

#### Commit Detail
Expand All @@ -223,14 +227,15 @@ The default key bindings can be overridden. Please refer to [default-keybind.tom

#### Refs List

| Key | Description | Corresponding keybind |
| -------------------------------------------------- | ---------------- | ---------------------------------- |
| <kbd>Esc</kbd> <kbd>Backspace</kbd> <kbd>Tab</kbd> | Close refs list | `close` `cancel` `ref_list_toggle` |
| <kbd>Down/Up</kbd> <kbd>j/k</kbd> | Move down/up | `navigate_down` `navigate_up` |
| <kbd>J/K</kbd> | Move down/up | `select_down` `select_up` |
| <kbd>g/G</kbd> | Go to top/bottom | `go_to_top` `go_to_bottom` |
| <kbd>Right/Left</kbd> <kbd>l/h</kbd> | Open/Close node | `navigate_right` `navigate_left` |
| <kbd>c</kbd> | Copy ref name | `short_copy` |
| Key | Description | Corresponding keybind |
| -------------------------------------------------- | ------------------------- | ---------------------------------- |
| <kbd>Esc</kbd> <kbd>Backspace</kbd> <kbd>Tab</kbd> | Close refs list | `close` `cancel` `ref_list_toggle` |
| <kbd>Down/Up</kbd> <kbd>j/k</kbd> | Move down/up | `navigate_down` `navigate_up` |
| <kbd>J/K</kbd> | Move down/up | `select_down` `select_up` |
| <kbd>g/G</kbd> | Go to top/bottom | `go_to_top` `go_to_bottom` |
| <kbd>Right/Left</kbd> <kbd>l/h</kbd> | Open/Close node | `navigate_right` `navigate_left` |
| <kbd>c</kbd> | Copy ref name | `short_copy` |
| <kbd>d</kbd> | Delete branch/tag | `user_command_view_toggle_1` |

#### User Command

Expand Down
8 changes: 7 additions & 1 deletion assets/default-keybind.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,17 @@ go_to_previous = ["shift-n"]
confirm = ["enter"]
ref_list_toggle = ["tab"]
search = ["/"]
ignore_case_toggle = ["ctrl-g"]
filter = ["f"]
ignore_case_toggle = ["alt-c"]
fuzzy_toggle = ["ctrl-x"]

user_command_view_toggle_1 = ["d"]

# copy part of information, ex: copy the short commit hash not all
short_copy = ["c"]
full_copy = ["shift-c"]

create_tag = ["t"]
delete_tag = ["ctrl-t"]

refresh = ["r"]
259 changes: 259 additions & 0 deletions scripts/generate_test_repo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
#!/bin/bash
#
# Generate test git repository with realistic branch patterns
#
# Usage: ./generate_test_repo.sh [path] [commit_count]
# ./generate_test_repo.sh /tmp/my-repo 5000
#
# Creates a repository with:
# - Main branch with linear history
# - feature/xxx and bugfix/xxx branches forking from main
# - Each branch lives 2-8 commits before merging back
# - Merges use --no-ff to preserve merge commits
# - Max 5 concurrent branches
# - Semver tags (v0.1.0, v0.2.3, ...)
# - Multiple authors with realistic dates spread over 2 years
#
# Resulting graph looks like typical "christmas tree":
#
# * merge feature/auth-42
# |\
# | * feat(auth): add auth logic
# | * feat(auth): implement auth logic
# |/
# * fix: merge bugfix/timeout-15
# |\
# | * fix(api): fix api logic
# |/
# * feat(cache): update cache logic
# * chore: initial commit
#

set -e

REPO_DIR="${1:-/tmp/test-repo-10k}"
COMMIT_COUNT="${2:-10000}"

rm -rf "$REPO_DIR"
mkdir -p "$REPO_DIR"
cd "$REPO_DIR"
git init
git config user.email "[email protected]"
git config user.name "Developer"

# Arrays for random content
AUTHORS=(
"Alice Smith:[email protected]"
"Bob Johnson:[email protected]"
"Charlie Brown:[email protected]"
"Diana Prince:[email protected]"
"Eve Wilson:[email protected]"
)

PREFIXES=("feat" "fix" "refactor" "docs" "test" "chore" "perf")

FEATURES=(
"auth" "api" "database" "cache" "search" "upload" "export"
"notifications" "settings" "dashboard" "reports" "billing"
"users" "permissions" "logging" "metrics" "config" "cli"
)

BUGS=(
"login-redirect" "null-pointer" "memory-leak" "timeout" "encoding"
"validation" "race-condition" "deadlock" "overflow" "parsing"
"connection-drop" "cache-invalidation" "timezone" "locale"
)

ACTIONS=("add" "update" "fix" "improve" "implement" "refactor" "optimize")

random_element() {
local arr=("$@")
echo "${arr[$RANDOM % ${#arr[@]}]}"
}

random_author() {
local info=$(random_element "${AUTHORS[@]}")
echo "$info"
}

generate_commit_message() {
local prefix=$(random_element "${PREFIXES[@]}")
local feature=$(random_element "${FEATURES[@]}")
local action=$(random_element "${ACTIONS[@]}")
echo "$prefix($feature): $action ${feature} logic"
}

generate_file_content() {
echo "// Updated at $(date +%s%N)"
echo "// Random: $RANDOM"
for i in {1..10}; do
echo "fn func_$RANDOM() { /* ... */ }"
done
}

make_commit() {
local message="$1"
local author_info="$2"
local commit_date="$3"

local author_name="${author_info%%:*}"
local author_email="${author_info##*:}"

local component=$(random_element "${FEATURES[@]}")
local file_path="src/${component}.rs"
mkdir -p "$(dirname "$file_path")"
generate_file_content > "$file_path"
git add "$file_path"

GIT_AUTHOR_NAME="$author_name" \
GIT_AUTHOR_EMAIL="$author_email" \
GIT_AUTHOR_DATE="$commit_date" \
GIT_COMMITTER_NAME="$author_name" \
GIT_COMMITTER_EMAIL="$author_email" \
GIT_COMMITTER_DATE="$commit_date" \
git commit -m "$message" --quiet 2>/dev/null || true
}

echo "Generating $COMMIT_COUNT commits in $REPO_DIR..."

# Initial commit
mkdir -p src
echo "# Test Repository" > README.md
echo "fn main() {}" > src/main.rs
git add .
git commit -m "chore: initial commit" --quiet

start_time=$(date +%s)
base_date=$(date -d "2023-01-01" +%s 2>/dev/null || date -j -f "%Y-%m-%d" "2023-01-01" +%s 2>/dev/null || echo "1672531200")
seconds_per_commit=$(( 730 * 24 * 3600 / COMMIT_COUNT )) # spread over 2 years

commit_num=0
active_branches=()
next_branch_at=$((RANDOM % 20 + 5))
branch_counter=0
version_major=0
version_minor=0
version_patch=0

while [ $commit_num -lt $COMMIT_COUNT ]; do
# Progress
if [ $((commit_num % 500)) -eq 0 ] && [ $commit_num -gt 0 ]; then
elapsed=$(($(date +%s) - start_time))
rate=$((commit_num / (elapsed + 1)))
echo "Progress: $commit_num/$COMMIT_COUNT ($rate/sec)"
fi

current_date=$((base_date + commit_num * seconds_per_commit + RANDOM % 3600))
commit_date=$(date -d "@$current_date" --iso-8601=seconds 2>/dev/null || date -r "$current_date" +%Y-%m-%dT%H:%M:%S 2>/dev/null || echo "2023-06-15T12:00:00")
author=$(random_author)

# Decide: work on main or create/continue feature branch
if [ ${#active_branches[@]} -eq 0 ] || [ $((RANDOM % 3)) -eq 0 ]; then
# Work on main
git checkout main --quiet 2>/dev/null || git checkout -b main --quiet

# Maybe start a new branch
if [ $commit_num -ge $next_branch_at ] && [ ${#active_branches[@]} -lt 5 ]; then
branch_counter=$((branch_counter + 1))

# Feature or bugfix?
if [ $((RANDOM % 3)) -eq 0 ]; then
bug=$(random_element "${BUGS[@]}")
branch_name="bugfix/${bug}-${branch_counter}"
branch_type="bugfix"
else
feature=$(random_element "${FEATURES[@]}")
branch_name="feature/${feature}-${branch_counter}"
branch_type="feature"
fi

git checkout -b "$branch_name" --quiet
active_branches+=("$branch_name:$branch_type:1")
next_branch_at=$((commit_num + RANDOM % 30 + 10))

make_commit "$(generate_commit_message)" "$author" "$commit_date"
commit_num=$((commit_num + 1))
else
# Regular main commit
make_commit "$(generate_commit_message)" "$author" "$commit_date"
commit_num=$((commit_num + 1))

# Maybe tag a release
if [ $((RANDOM % 100)) -eq 0 ]; then
version_patch=$((version_patch + 1))
if [ $version_patch -ge 10 ]; then
version_patch=0
version_minor=$((version_minor + 1))
fi
if [ $version_minor -ge 10 ]; then
version_minor=0
version_major=$((version_major + 1))
fi
git tag "v${version_major}.${version_minor}.${version_patch}" 2>/dev/null || true
fi
fi
else
# Work on existing branch
idx=$((RANDOM % ${#active_branches[@]}))
branch_info="${active_branches[$idx]}"
branch_name="${branch_info%%:*}"
rest="${branch_info#*:}"
branch_type="${rest%%:*}"
branch_commits="${rest##*:}"

git checkout "$branch_name" --quiet 2>/dev/null || continue

# Add commit to branch
make_commit "$(generate_commit_message)" "$author" "$commit_date"
commit_num=$((commit_num + 1))
branch_commits=$((branch_commits + 1))

# Update branch info
active_branches[$idx]="$branch_name:$branch_type:$branch_commits"

# Maybe merge back to main (after 2-8 commits)
if [ $branch_commits -ge $((RANDOM % 7 + 2)) ]; then
git checkout main --quiet

# Merge with descriptive message
if [ "$branch_type" = "bugfix" ]; then
merge_msg="fix: merge $branch_name"
else
merge_msg="feat: merge $branch_name"
fi

git merge --no-ff "$branch_name" -m "$merge_msg" --quiet 2>/dev/null || {
git merge --abort 2>/dev/null || true
git checkout main --quiet
}

git branch -d "$branch_name" --quiet 2>/dev/null || true

# Remove from active branches
unset 'active_branches[$idx]'
active_branches=("${active_branches[@]}")
fi
fi
done

# Merge remaining branches
git checkout main --quiet 2>/dev/null || true
for branch_info in "${active_branches[@]}"; do
branch_name="${branch_info%%:*}"
if git show-ref --verify --quiet "refs/heads/$branch_name" 2>/dev/null; then
git merge --no-ff "$branch_name" -m "feat: merge $branch_name" --quiet 2>/dev/null || git merge --abort 2>/dev/null || true
git branch -d "$branch_name" --quiet 2>/dev/null || true
fi
done

end_time=$(date +%s)
elapsed=$((end_time - start_time))

echo ""
echo "Done!"
echo "Repository: $REPO_DIR"
echo "Commits: $(git rev-list --count HEAD)"
echo "Tags: $(git tag | wc -l)"
echo "Time: ${elapsed}s"
echo ""
echo "Test with: serie $REPO_DIR"
Loading