From aad97d949ac0cac085e8cbcf1e34c7e59d17fdc0 Mon Sep 17 00:00:00 2001 From: Astound Date: Mon, 3 Nov 2025 16:27:38 -0500 Subject: Optimized git --- .gitea/workflows/ci.yaml | 100 ++++++++++- hyperterm/core/autodep.sh | 7 +- hyperterm/core/git.sh | 436 ++++++++++++--------------------------------- hyperterm/hyperterm.sha512 | 4 +- tests/README.md | 84 +++++++++ tests/quick_test.sh | 176 ++++++++++++++++++ tests/test_prompt.sh | 304 +++++++++++++++++++++++++++++++ 7 files changed, 778 insertions(+), 333 deletions(-) create mode 100644 tests/README.md create mode 100644 tests/quick_test.sh create mode 100644 tests/test_prompt.sh diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 4c181ca..a5e2ca9 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -1,12 +1,17 @@ name: CI Pipeline -on: [push, pull_request] +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + workflow_dispatch: jobs: shasums: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run shasums script run: | cp -rv ./hyperterm/ "$HOME/.hyperterm/" @@ -79,6 +84,11 @@ jobs: shellcheck hyperterm/tools/sysinfo.sh shellcheck hyperterm/tools/virtualenv.sh + - name: Run shellcheck on test scripts + run: | + shellcheck tests/test_prompt.sh + shellcheck tests/quick_test.sh + - name: Run shellcheck on install script run: shellcheck install.sh @@ -90,3 +100,89 @@ jobs: - name: Run uninstall script run: bash -x uninstall.sh -s + + tests: + runs-on: ubuntu-latest + needs: build + env: + CI: true + steps: + - uses: actions/checkout@v4 + + - name: Set up git configuration for tests + run: | + git config --global user.name "CI Test User" + git config --global user.email "ci-test@example.com" + + - name: Install dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y git bash + + - name: Run quick prompt test + run: | + echo "INFO: Running quick prompt test" + bash tests/quick_test.sh + + - name: Run comprehensive prompt test + run: | + echo "INFO: Running comprehensive prompt test" + # Run in non-interactive mode for CI + bash tests/test_prompt.sh --non-interactive + + - name: Test prompt in current repository + run: | + echo "INFO: Testing prompt in current git repository" + # Create test changes to verify git status detection + echo "test change for CI" >> README.md + echo "untracked CI test file" > ci_test_file.txt + + # Test using our quick test (which already handles sourcing correctly) + echo "Running quick test to verify prompt works in CI environment..." + bash tests/quick_test.sh + + - name: Validate optimizations + run: | + echo "INFO: Validating optimizations" + + # Check that git_optimized.sh doesn't exist (should be integrated into git.sh) + if [[ -f "hyperterm/core/git_optimized.sh" ]]; then + echo "ERROR: git_optimized.sh should not exist (should be integrated)" + exit 1 + fi + + # Check that git.sh exists and is optimized + if [[ ! -f "hyperterm/core/git.sh" ]]; then + echo "ERROR: git.sh not found" + exit 1 + fi + + # Verify the optimized functions exist using bash + if ! bash -c 'source hyperterm/core/git.sh && command -v _get_git_status_fast >/dev/null 2>&1'; then + echo "ERROR: Optimized function _get_git_status_fast not found" + exit 1 + fi + + echo "SUCCESS: Optimizations validated" + + performance: + runs-on: ubuntu-latest + needs: tests + steps: + - uses: actions/checkout@v4 + + - name: Set up git configuration + run: | + git config --global user.name "Performance Test" + git config --global user.email "perf-test@example.com" + + - name: Performance benchmark + run: | + echo "INFO: Running performance benchmark using existing test suite" + + # The comprehensive test already includes performance testing + # Run it again to get performance metrics in CI logs + bash tests/test_prompt.sh --non-interactive + + echo "INFO: Performance benchmark completed" + echo "NOTE: Detailed performance metrics are included in the comprehensive test output above" diff --git a/hyperterm/core/autodep.sh b/hyperterm/core/autodep.sh index 8973100..26a1a3b 100644 --- a/hyperterm/core/autodep.sh +++ b/hyperterm/core/autodep.sh @@ -10,9 +10,10 @@ function install_package() { msg "El paquete $pkg no se encontró. Procediendo a instalar..." \ "$pkg not found. Attempting to install..." - local INSTALLER="" - local SUDO="" - local USER_CMD=$(command -v sudo || command -v doas) + local INSTALLER + local SUDO + local USER_CMD + USER_CMD=$(command -v sudo || command -v doas)=$(command -v sudo || command -v doas) [[ "$(id -u)" -ne 0 ]] && SUDO="$USER_CMD" diff --git a/hyperterm/core/git.sh b/hyperterm/core/git.sh index 3ae80c2..4e437ef 100644 --- a/hyperterm/core/git.sh +++ b/hyperterm/core/git.sh @@ -1,373 +1,157 @@ #!/bin/bash -# Set up symbols -function _symbols() { +# Cache symbols (generate once) +declare -A GIT_SYMBOLS +_init_git_symbols() { + [[ -n "${GIT_SYMBOLS[clean]:-}" ]] && return # Already initialized - # Import colors _colors_bash "$@" - __ps="$(printf '%b%b%b' "${BOLD}${LEMON}" "\x7C" "${RESET}")" # | - __ss="$(printf '%b%b' "${BOLD}${CYAN}" "\xE2\x9C\x94")" # ✔ - __dss="$(printf '%b%b' "${BOLD}${RED}" "\x2A")" # ∗ - __ahs="$(printf '%b%b' "${BOLD}${CYAN}" "\xE2\x86\x91")" # ↑ - __bhs="$(printf '%b%b' "${BOLD}${RED}" "\xE2\x86\x93")" # ↓ - __duphs="$(printf '%b%b' "${BOLD}${YELLOW}" "\xE2\x96\x82" )" # ▲ - __duplls="$(printf '%b%b' "${BOLD}${RED}" "\xE2\x96\xBC")" # ▼ - __duus="$(printf '%b%b%b' "${BOLD}${CYAN}" "\x64" "\x75")" # du - __upulls="$(printf '%b%b' "${BOLD}${GREEN}" "\xE2\x96\xBD")" # ▽ - __sts="$(printf '%b%b%b' "${BOLD}${CYAN}" "\xE2\x86\x92" "\x4D")" # →M - __usts="$(printf '%b%b%b' "${BOLD}${RED}" "\xE2\x86\x90" "\x4D")" # ←M - __stusts="$(printf '%b%b%b%b' "${BOLD}${RED}" "\x3C" "\x4D" "\x3E")" # - __uts="$(printf '%b%b' "${BOLD}${RED}" "\x3F")" # ? - __nfs="$(printf '%b%b' "${BOLD}${CYAN}" "\x2B")" # + - __dfs="$(printf '%b%b' "${BOLD}${RED}" "\x44")" # D - __rns="$(printf '%b%b%b' "${BOLD}${RED}" "\xE2\x8E\x87" "\x20")" # ⎇ + GIT_SYMBOLS[pipe]="\x7C" + GIT_SYMBOLS[clean]="✔" + GIT_SYMBOLS[dirty]="∗" + GIT_SYMBOLS[ahead]="↑" + GIT_SYMBOLS[behind]="↓" + GIT_SYMBOLS[diverged]="⇅" + GIT_SYMBOLS[untracked]="?" + GIT_SYMBOLS[added]="+" + GIT_SYMBOLS[deleted]="D" + GIT_SYMBOLS[modified]="M" + GIT_SYMBOLS[renamed]="R" + GIT_SYMBOLS[staged]="●" } -function _get_git_branch() { - # On branches, this will return the branch name - # On non-branches, (no branch) - ref="$(git symbolic-ref HEAD 2> /dev/null | sed -e 's/refs\/heads\///')" - if [[ -n $ref ]]; then - printf '%s' "$ref" - else - printf "(no branch)" - fi -} - -function _get_git_progress() { - # Detect in-progress actions (e.g. merge, rebase) - # https://github.com/git/git/blob/v1.9-rc2/wt-status.c#L1199-L1241 - git_dir="$(git rev-parse --git-dir)" - - # git merge - if [[ -f "$git_dir/MERGE_HEAD" ]]; then - printf " [merge]" - elif [[ -d "$git_dir/rebase-apply" ]]; then - # git am - if [[ -f "$git_dir/rebase-apply/applying" ]]; then - printf " [am]" - # git rebase - else - printf " [rebase]" - fi - elif [[ -d "$git_dir/rebase-merge" ]]; then - # git rebase --interactive/--merge - printf " [rebase]" - elif [[ -f "$git_dir/CHERRY_PICK_HEAD" ]]; then - # git cherry-pick - printf " [cherry-pick]" - fi - if [[ -f "$git_dir/BISECT_LOG" ]]; then - # git bisect - printf " [bisect]" - fi - if [[ -f "$git_dir/REVERT_HEAD" ]]; then - # git revert --no-commit - printf " [revert]" - fi +_get_git_branch() { + git symbolic-ref --short HEAD 2>/dev/null || echo "(no branch)" } -_prompt_is_branch1_behind_branch2 () { - # $ git log origin/master..master -1 - # commit 4a633f715caf26f6e9495198f89bba20f3402a32 - # Author: Todd Wolfson - # Date: Sun Jul 7 22:12:17 2013 -0700 - # - # Unsynced commit +_get_git_progress() { + local git_dir + git_dir="$(git rev-parse --git-dir 2>/dev/null)" || return 0 - # Find the first log (if any) that is in branch1 but not branch2 - first_log="$(git log "$1..$2" -1 2> /dev/null)" + [[ -f "$git_dir/MERGE_HEAD" ]] && echo " [merge]" && return 0 + [[ -d "$git_dir/rebase-apply" ]] && echo " [rebase]" && return 0 + [[ -d "$git_dir/rebase-merge" ]] && echo " [rebase]" && return 0 + [[ -f "$git_dir/CHERRY_PICK_HEAD" ]] && echo " [cherry-pick]" && return 0 + [[ -f "$git_dir/BISECT_LOG" ]] && echo " [bisect]" && return 0 + [[ -f "$git_dir/REVERT_HEAD" ]] && echo " [revert]" && return 0 - # Exit with 0 if there is a first log, 1 if there is not - [[ -n "$first_log" ]] + return 0 } -_prompt_branch_exists () { - # List remote branches | # Find our branch and exit with 0 or 1 if found/not found - git branch --remote 2> /dev/null | grep --quiet "$1" -} +# Single git status call - parse everything at once +_parse_git_status() { + local status_output ahead_behind -_prompt_parse_git_ahead () { - # Grab the local and remote branch - branch="$(_get_git_branch)" - remote="$(git config --get "branch.${branch}.remote" || echo -n "origin")" - remote_branch="$remote/$branch" + # Single git status call + status_output="$(git status --porcelain=v1 2>/dev/null)" - # $ git log origin/master..master - # commit 4a633f715caf26f6e9495198f89bba20f3402a32 - # Author: Todd Wolfson - # Date: Sun Jul 7 22:12:17 2013 -0700 - # - # Unsynced commit + # Get ahead/behind counts in one call + if git rev-parse --abbrev-ref "@{upstream}" >/dev/null 2>&1; then + ahead_behind="$(git rev-list --left-right --count "@{upstream}"...HEAD 2>/dev/null)" + GIT_STATUS[behind]="${ahead_behind% *}" + GIT_STATUS[ahead]="${ahead_behind#* }" + else + GIT_STATUS[behind]=0 + GIT_STATUS[ahead]=0 + fi - # If the remote branch is behind the local branch - # or it has not been merged into origin (remote branch doesn't exist) - if (_prompt_is_branch1_behind_branch2 "$remote_branch" "$branch" || ! _prompt_branch_exists "$remote_branch"); then echo -n "0"; else echo -n "1"; fi -} + # Parse status output + GIT_STATUS[dirty]=0 + GIT_STATUS[staged]=0 + GIT_STATUS[untracked]=0 + GIT_STATUS[modified]=0 + GIT_STATUS[added]=0 + GIT_STATUS[deleted]=0 + GIT_STATUS[renamed]=0 -_prompt_parse_git_behind() { - # Grab the branch - branch="$(_get_git_branch)" - remote="$(git config --get "branch.${branch}.remote" || echo -n "origin")" - remote_branch="$remote/$branch" + [[ -z "$status_output" ]] && return - # $ git log master..origin/master - # commit 4a633f715caf26f6e9495198f89bba20f3402a32 - # Author: Todd Wolfson - # Date: Sun Jul 7 22:12:17 2013 -0700 - # - # Unsynced commit + local line + while IFS= read -r line; do + [[ -z "$line" ]] && continue - # If the local branch is behind the remote branch - if _prompt_is_branch1_behind_branch2 "$branch" "$remote_branch"; then echo -n '0'; else echo -n '1'; fi -} + local index_status="${line:0:1}" + local work_status="${line:1:1}" -function _prompt_parse_git_dirty() { - # If the git status has *any* changes (e.g. dirty), printf our character - if [[ -n "$(git status --porcelain 2> /dev/null)" ]]; then echo -n '0'; else echo -n '1'; fi -} + # Count changes + ((GIT_STATUS[dirty]++)) -# start counter on git -function _git_dirty_count() { - local _dirty_status - local _git_status - _dirty_status="$(_prompt_parse_git_dirty)" - _git_status="$(git status --porcelain 2> /dev/null)" - if [[ "$_dirty_status" == 0 ]]; then - local change_count - change_count="$(echo "$_git_status" | wc -l | tr -d '[:space:]')" - case $change_count in - 1) printf '%b\u2022%s' "${BOLD}${GREY}" "$change_count";; - 2) printf '%b\u2236%s' "${BOLD}${GREY}" "$change_count";; - 3) printf '%b\u2026%s' "${BOLD}${GREY}" "$change_count";; - *) printf '%b\u00BB%s' "${BOLD}${GREY}" "$change_count";; + # Index (staged) changes + case "$index_status" in + [MADRC]) ((GIT_STATUS[staged]++)) ;; esac - fi -} -function _git_behind_count() { - local __behind_count - if git rev-parse --symbolic-full-name --abbrev-ref "@{upstream}" > /dev/null 2>&1; then - __behind_count="$(git rev-list --left-right --count "@{upstream}"...HEAD | cut -f1 2> /dev/null)" - case $__behind_count in - 0) echo -n '';; - *) echo -n "$__behind_count";; + # Working tree changes + case "$work_status" in + M) ((GIT_STATUS[modified]++)) ;; + D) ((GIT_STATUS[deleted]++)) ;; esac - fi -} -function _git_ahead_count() { - local __ahead_count - if git rev-parse --symbolic-full-name --abbrev-ref "@{upstream}" > /dev/null 2>&1; then - __ahead_count="$(git rev-list --left-right --count "@{upstream}"...HEAD | cut -f2 2> /dev/null)" - case $__ahead_count in - 0) echo -n '';; - *) echo -n "$__ahead_count";; + # Special cases + case "$line" in + "??*") ((GIT_STATUS[untracked]++)) ;; + "A "*) ((GIT_STATUS[added]++)) ;; + "R "*) ((GIT_STATUS[renamed]++)) ;; esac - fi -} -# ends counter on git - -function _prompt_parse_git_untracked() { - local untracked - local evaltask - untracked="$(git status 2>&1 | tee)" - grep -E 'Untracked files:' <<<"$untracked" &> /dev/null - evaltask=$? - if [ "$evaltask" -eq 0 ]; then echo -n '0'; else echo -n '1'; fi -} -function _prompt_parse_git_newfile() { - local newfile - local evaltask - newfile="$(git status 2>&1 | tee)" - grep -E 'new file:' <<<"$newfile" &> /dev/null - evaltask=$? - if [ "$evaltask" -eq 0 ]; then echo -n '0'; else echo -n '1'; fi + done <<< "$status_output" } -function _prompt_parse_git_deleted_file() { - local deleted_file - local evaltask - deleted_file="$(git status 2>&1 | tee)" - grep -E 'deleted:' <<<"$deleted_file" &> /dev/null - evaltask=$? - if [ "$evaltask" -eq 0 ]; then echo -n '0'; else echo -n '1'; fi -} +# Fast git status generation +_get_git_status_fast() { + _init_git_symbols "$@" -function _prompt_parse_git_renamed() { - local renamed - local evaltask - renamed="$(git status 2>&1 | tee)" - grep -E 'renamed:' <<<"$renamed" &> /dev/null - evaltask=$? - if [ "$evaltask" -eq 0 ]; then echo -n '0'; else echo -n '1'; fi -} + declare -A GIT_STATUS + _parse_git_status -function _prompt_parse_git_unstage() { - local unstage - local evaltask - unstage="$(git status 2>&1 | tee)" - grep -E 'not staged' <<<"$unstage" &> /dev/null - evaltask=$? - if [ "$evaltask" -eq 0 ]; then echo -n '0'; else echo -n '1'; fi -} - -function _prompt_parse_git_stage() { - local stage - local evaltask - stage="$(git status -s 2>&1 | tee)" - grep -E 'M' <<<"$stage" &> /dev/null - evaltask=$? - if [ "$evaltask" -eq 0 ]; then echo -n '0'; else echo -n '1'; fi -} + local output="" -function _prompt_is_on_git() { - git rev-parse 2> /dev/null -} - -function _prompt_get_git_status() { - - _symbols "$@" - - # Grab the git dirty and git behind - count_dirty="$(_git_dirty_count)" - count_behind="$(_git_behind_count)" - count_ahead="$(_git_ahead_count)" - dirty_branch="$(_prompt_parse_git_dirty)" - branch_ahead="$(_prompt_parse_git_ahead)" - branch_behind="$(_prompt_parse_git_behind)" - branch_stage="$(_prompt_parse_git_stage)" - branch_unstage="$(_prompt_parse_git_unstage)" - branch_untracked="$(_prompt_parse_git_untracked)" - branch_newfile="$(_prompt_parse_git_newfile)" - branch_deleted_file="$(_prompt_parse_git_deleted_file)" - branch_renamed="$(_prompt_parse_git_renamed)" - - # Iterate through all the cases and if it matches, then printf - case ${dirty_branch}${branch_ahead}${branch_behind}${branch_stage}${branch_unstage}${branch_newfile}${branch_untracked}${branch_deleted_file}${branch_renamed} in - 111111111) printf '%s' "${__ss}";; - 100111111) printf '%s' "${__ps}${__ahs}$count_ahead${__ps}${__bhs}$count_behind";; - 110111111) printf '%s%s' "${__upulls}" "$count_behind";; - 101111111) printf '%s%s' "${__ahs}" "$count_ahead";; - 111001111) printf '%s%s' "${__ps}${__duus}${__stusts}" "$count_dirty";; - 011111111) printf '%s%s' "${__dss}" "$count_dirty";; - 010111111) printf '%s%s%s' "${__duplls}" "$count_behind" "$count_dirty";; - 001111111) printf '%s%s%s' "${__duphs}" "$count_ahead" "$count_dirty";; - 000111111) printf '%s%s%s' "${__duus}" "$count_behind-$count_ahead" "$count_dirty";; - - 000111011) printf '%s%s' "${__ps}${__ahs}$count_ahead${__ps}${__bhs}$count_behind${__ps}${__uts}${__ps}" "$count_dirty" ;; + # Clean repo + if [[ ${GIT_STATUS[dirty]} -eq 0 ]]; then + echo -n "${BOLD}${CYAN}${GIT_SYMBOLS[clean]}${RESET}" + return + fi - 010111011) printf '%s%s' "${__ps}${__bhs}$count_behind${__ps}${__dss}${__uts}" "${__ps}$count_dirty";; - 010110111) printf '%s%s' "${__ps}${__bhs}$count_behind${__ps}${__dss}${__nfs}" "${__ps}$count_dirty";; - 010110011) printf '%s%s' "${__ps}${__bhs}$count_behind${__ps}${__dss}${__nfs}${__uts}" "${__ps}$count_dirty";; - 010100001) printf '%s%s' "${__ps}${__bhs}$count_behind${__ps}${__dss}${__nfs}${__usts}${__uts}${__dfs}" "${__ps}$count_dirty";; - 010000001) printf '%s%s' "${__ps}${__bhs}$count_behind${__ps}${__dss}${__nfs}${__stusts}${__uts}${__dfs}" "${__ps}$count_dirty";; - 010010100) printf '%s%s' "${__ps}${__bhs}$count_behind${__ps}${__dss}${__nfs}${__dfs}${__rns}" "${__ps}${__sts}${__ps}$count_dirty";; - 010010000) printf '%s%s' "${__ps}${__bhs}$count_behind${__ps}${__dss}${__nfs}${__dfs}${__rns}${__ps}${__uts}" "${__ps}${__sts}${__ps}$count_dirty";; + # Dirty count + output="${BOLD}${RED}${GIT_SYMBOLS[dirty]}${GIT_STATUS[dirty]}${RESET}" - 011001111) printf '%s%s' "${__ps}${__dss}${__stusts}" "$count_dirty";; - 011000111) printf '%s%s' "${__ps}${__dss}${__stusts}${__nfs}" "$count_dirty";; - 011001101) printf '%s%s' "${__ps}${__dss}${__stusts}${__dfs}" "$count_dirty";; - 011001011) printf '%s%s' "${__ps}${__dss}${__stusts}${__uts}" "$count_dirty" ;; - 011001001) printf '%s%s' "${__ps}${__dss}${__stusts}${__uts}${__dfs}" "$count_dirty";; - 011000101) printf '%s%s' "${__ps}${__dss}${__stusts}${__nfs}${__dfs}" "$count_dirty";; - 011000001) printf '%s%s' "${__ps}${__dss}${__stusts}${__nfs}${__uts}${__dfs}" "$count_dirty";; - 011011111) printf '%s%s' "${__ps}${__dss}${__sts}" "$count_dirty";; - 011010111) printf '%s%s' "${__ps}${__dss}${__sts}${__nfs}" "$count_dirty";; - 011010011) printf '%s%s' "${__ps}${__dss}${__sts}${__nfs}${__uts}" "$count_dirty";; - 011010101) printf '%s%s' "${__ps}${__dss}${__sts}${__nfs}${__dfs}" "$count_dirty" ;; - 011010001) printf '%s%s' "${__ps}${__dss}${__sts}${__nfs}${__uts}${__dfs}" "$count_dirty";; - 011011011) printf '%s%s' "${__ps}${__dss}${__sts}${__uts}" "$count_dirty";; - 011011101) printf '%s%s' "${__ps}${__dss}${__sts}${__dfs}" "$count_dirty";; - 011110111) printf '%s%s' "${__ps}${__dss}${__nfs}" "$count_dirty";; - 011110011) printf '%s%s' "${__ps}${__dss}${__nfs}${__uts}" "$count_dirty";; - 011111011) printf '%s%s' "${__ps}${__dss}${__uts}" "$count_dirty";; - 011101001) printf '%s%s' "${__ps}${__dss}${__usts}${__uts}${__dfs}" "$count_dirty";; - 011111110) printf '%s%s' "${__ps}${__dss}${__rns}" "$count_dirty";; - 011110110) printf '%s%s' "${__ps}${__dss}${__nfs}${__rns}" "$count_dirty";; - 011110010) printf '%s%s' "${__ps}${__dss}${__nfs}${__uts}${__rns}" "$count_dirty";; - 011011110) printf '%s%s' "${__ps}${__dss}${__sts}${__rns}" "$count_dirty";; - 011010100) printf '%s%s' "${__ps}${__dss}${__sts}${__nfs}${__dfs}${__rns}" "$count_dirty" ;; - 011010000) printf '%s%s' "${__ps}${__dss}${__sts}${__nfs}${__uts}${__dfs}${__rns}" "$count_dirty";; - 011001010) printf '%s%s' "${__ps}${__dss}${__stusts}${__uts}${__rns}" "$count_dirty";; - 011001000) printf '%s%s' "${__ps}${__dss}${__stusts}${__uts}${__dfs}${__rns}" "$count_dirty";; - 011000011) printf '%s%s' "${__ps}${__dss}${__stusts}${__nfs}${__uts}" "$count_dirty";; - 011000110) printf '%s%s' "${__ps}${__dss}${__stusts}${__nfs}${__rns}" "$count_dirty";; - 011000010) printf '%s%s' "${__ps}${__dss}${__stusts}${__nfs}${__uts}${__rns}" "$count_dirty";; - 011000000) printf '%s%s' "${__ps}${__dss}${__stusts}${__nfs}${__uts}${__dfs}${__rns}" "$count_dirty";; - 011000100) printf '%s%s' "${__ps}${__dss}${__stusts}${__nfs}${__dfs}${__rns}" "$count_dirty";; - 011010010) printf '%s%s' "${__ps}${__dss}${__sts}${__nfs}${__uts}${__rns}" "$count_dirty";; - 011011010) printf '%s%s' "${__ps}${__dss}${__sts}${__uts}${__rns}" "$count_dirty";; - 011111010) printf '%s%s' "${__ps}${__dss}${__uts}${__rns}" "$count_dirty";; + # Ahead/behind + if [[ ${GIT_STATUS[ahead]} -gt 0 && ${GIT_STATUS[behind]} -gt 0 ]]; then + output+="${BOLD}${YELLOW}${GIT_SYMBOLS[diverged]}${GIT_STATUS[ahead]}/${GIT_STATUS[behind]}${RESET}" + elif [[ ${GIT_STATUS[ahead]} -gt 0 ]]; then + output+="${BOLD}${GREEN}${GIT_SYMBOLS[ahead]}${GIT_STATUS[ahead]}${RESET}" + elif [[ ${GIT_STATUS[behind]} -gt 0 ]]; then + output+="${BOLD}${RED}${GIT_SYMBOLS[behind]}${GIT_STATUS[behind]}${RESET}" + fi - 001001111) printf '%s%s' "${__ps}${__duus}${__stusts}" "$count_dirty";; - 001000111) printf '%s%s' "${__ps}${__duus}${__stusts}${__nfs}" "$count_dirty";; - 001001101) printf '%s%s' "${__ps}${__duus}${__stusts}${__dfs}" "$count_dirty";; - 001001011) printf '%s%s' "${__ps}${__duus}${__stusts}${__uts}" "$count_dirty";; - 001001001) printf '%s%s' "${__ps}${__duus}${__stusts}${__uts}${__dfs}" "$count_dirty";; - 001000101) printf '%s%s' "${__ps}${__duus}${__stusts}${__nfs}${__dfs}" "$count_dirty";; - 001000001) printf '%s%s' "${__ps}${__duus}${__stusts}${__nfs}${__uts}${__dfs}" "$count_dirty";; - 001011111) printf '%s%s' "${__ps}${__duus}${__sts}" "$count_dirty";; - 001010111) printf '%s%s' "${__ps}${__duus}${__sts}${__nfs}" "$count_dirty" ;; - 001010011) printf '%s%s' "${__ps}${__duus}${__sts}${__nfs}${__uts}" "$count_dirty";; - 001010101) printf '%s%s' "${__ps}${__duus}${__sts}${__nfs}${__dfs}" "$count_dirty" ;; - 001010001) printf '%s%s' "${__ps}${__duus}${__sts}${__nfs}${__uts}${__dfs}" "$count_dirty";; - 001011011) printf '%s%s' "${__ps}${__duus}${__sts}${__uts}" "$count_dirty";; - 001011101) printf '%s%s' "${__ps}${__duus}${__sts}${__dfs}" "$count_dirty";; - 001110111) printf '%s%s' "${__ps}${__duus}${__nfs}" "$count_dirty";; - 001110011) printf '%s%s' "${__ps}${__duus}${__nfs}${__uts}" "$count_dirty";; - 001111011) printf '%s%s' "${__ps}${__duus}${__uts}" "$count_dirty";; - 001101001) printf '%s%s' "${__ps}${__duus}${__usts}${__uts}${__dfs}" "$count_dirty";; - 001101101) printf '%s%s' "${__ps}${__duus}${__usts}${__dfs}" "$count_dirty";; - 001111110) printf '%s%s' "${__ps}${__duus}${__rns}" "$count_dirty";; - 001110110) printf '%s%s' "${__ps}${__duus}${__nfs}${__rns}" "$count_dirty";; - 001110010) printf '%s%s' "${__ps}${__duus}${__nfs}${__uts}${__rns}" "$count_dirty";; - 001011110) printf '%s%s' "${__ps}${__duus}${__sts}${__rns}" "$count_dirty";; - 001010100) printf '%s%s' "${__ps}${__duus}${__sts}${__nfs}${__dfs}${__rns}" "$count_dirty" ;; - 001010000) printf '%s%s' "${__ps}${__duus}${__sts}${__nfs}${__uts}${__dfs}${__rns}" "$count_dirty";; - 001001010) printf '%s%s' "${__ps}${__duus}${__stusts}${__uts}${__rns}" "$count_dirty";; - 001001000) printf '%s%s' "${__ps}${__duus}${__stusts}${__uts}${__dfs}${__rns}" "$count_dirty";; - 001000011) printf '%s%s' "${__ps}${__duus}${__stusts}${__nfs}${__uts}" "$count_dirty";; - 001000110) printf '%s%s' "${__ps}${__duus}${__stusts}${__nfs}${__rns}" "$count_dirty";; - 001000010) printf '%s%s' "${__ps}${__duus}${__stusts}${__nfs}${__uts}${__rns}" "$count_dirty";; - 001000000) printf '%s%s' "${__ps}${__duus}${__stusts}${__nfs}${__uts}${__dfs}${__rns}" "$count_dirty";; - 001000100) printf '%s%s' "${__ps}${__duus}${__stusts}${__nfs}${__dfs}${__rns}" "$count_dirty";; - 001010010) printf '%s%s' "${__ps}${__duus}${__sts}${__nfs}${__uts}${__rns}" "$count_dirty";; - 001011010) printf '%s%s' "${__ps}${__duus}${__sts}${__uts}${__rns}" "$count_dirty";; - 001111010) printf '%s%s' "${__ps}${__duus}${__uts}${__rns}" "$count_dirty";; - *) echo -n "${__uts}" ;; - esac + # File status indicators + [[ ${GIT_STATUS[staged]} -gt 0 ]] && output+="${BOLD}${GREEN}${GIT_SYMBOLS[staged]}${RESET}" + [[ ${GIT_STATUS[untracked]} -gt 0 ]] && output+="${BOLD}${RED}${GIT_SYMBOLS[untracked]}${RESET}" + [[ ${GIT_STATUS[added]} -gt 0 ]] && output+="${BOLD}${GREEN}${GIT_SYMBOLS[added]}${RESET}" + [[ ${GIT_STATUS[deleted]} -gt 0 ]] && output+="${BOLD}${RED}${GIT_SYMBOLS[deleted]}${RESET}" + [[ ${GIT_STATUS[renamed]} -gt 0 ]] && output+="${BOLD}${BLUE}${GIT_SYMBOLS[renamed]}${RESET}" - # - # dirty + unpushed = du stage + unstage = - # ∗ ↑ ↓ →M ←M + ? D ⎇ - # echo "${dirty_branch}${branch_ahead}${branch_behind}${branch_stage}${branch_unstage}${branch_newfile}${branch_untracked}${branch_deleted_file}${branch_renamed}" - # 0 1 0 1 1 1 0 1 1 + echo -n "$output" } -_prompt_get_git_info() { - # Import colors +_prompt_get_git_info_fast() { _colors_bash "$@" - # Grab branch - branch=$(_get_git_branch) + local branch + branch="$(_get_git_branch)" - # If there are any branches - if [[ -n $branch ]]; then - # Add on the git status - output=$(_prompt_get_git_status "$@") - # Printf our output - printf '%b%s%b' "${BOLD}${LEMON}" "git:($branch$output" "${BOLD}${LEMON})" + if [[ -n "$branch" ]]; then + local status + status="$(_get_git_status_fast "$@")" + printf '%b%s%s%b' "${BOLD}${YELLOW}" "git:($branch" "$status" "${BOLD}${YELLOW})" fi } __prompt_git() { - if _prompt_is_on_git &> /dev/null; then - echo -n "${BOLD}${WHITE} on $RESET" && \ - echo -n "$(_prompt_get_git_info "$@")" && \ - echo -n "${BOLD}${RED}$(_get_git_progress)" && \ - echo -n "$RESET" + if git rev-parse --git-dir >/dev/null 2>&1; then + echo -n "${BOLD}${WHITE} on ${RESET}" + _prompt_get_git_info_fast "$@" + echo -n "${BOLD}${RED}$(_get_git_progress)${RESET}" fi } diff --git a/hyperterm/hyperterm.sha512 b/hyperterm/hyperterm.sha512 index 5733a0b..1ce287e 100644 --- a/hyperterm/hyperterm.sha512 +++ b/hyperterm/hyperterm.sha512 @@ -2,9 +2,9 @@ cdfe049ec07f02a1893cda29c13085d06709e09a30b0c2e1111585278315f03139d61080c883cb3f f363606f41a2c2c8f1cc44110c64fe23b1c8feb4c788ee006222db0f5c7a3adeac2b0948626b313adc985e9b8d303a0b9ce1c5ba42746810accb54efddcd4b84 ./hyperterm.sh b760a908a3f6222b974abc1f7464bde0f5427f120f1e7ef1c6d97ae61769e552ef3b5cb88e193e955da72a592f07eadb812413dd50a691cd3dbb33e3da581ea6 ./core/update.sh 1cfba599047d84a17ff92b695ebf527a505a30acc9ec21a2b9f410a7ea6dde4b23b5cf62e557d82f2fe9a8980649942424b879ca53baae4d4cb3057681baa7b6 ./core/colors.sh -2036a79215a5434e31f3406bea3f2ffa7e94ffef86c2d1ceb8865db29f19fe7f342f9cab93288f57c75daed36ef146f85d15f8d633931a27d55c3983f55ef15b ./core/git.sh +e3bacd715a327802c18de9b3c9f958f704eec4055295433403df284130e23f956f330bdf737c524bec3a89ef67a44ff0ab5dec40ffad5363aced3396bff54283 ./core/git.sh f3e00b2aa8ab9f3ab44570adaa2520408ed66fd00f551654d60b64a4be3546ec781b7efa39bcd774937e654b6ffb4c7af3f21eeb36caf9c01f82f85cf28e2b4d ./core/languages.sh -fdc570118a65a2b00571fd374be1aa983d41e9722bee6e604e2b00fcd2e1931df4dfd8951f15a2402dfcb3cbc76a5fe8d9d564a83fec73595dcaae2e38ac338a ./core/autodep.sh +b205de01644af11ef1dc96230e4bf12087482e26b7c0472fb6a153bf94662e4dfc01b2da0c3ca0da4af93bce05faf0e33be5422fbee85e6b69ca1cccbe194cff ./core/autodep.sh 7447d3e167ab207d3ef4218e201a06bf5a3fc23281639f16f7f405f1d66b73923845d450fdb0a94672757866a9da0324f728564a1b61b2ed1678fe576eb565cf ./core/autocomplete.sh 99f9e937e39495f60495bc89f055f29e1bee25911c9bea5c8b2a7c6a5bdc417502f17da8cb9e63ea957eea9337c6fe1c63c208a3ff3ed33ef2ca8c1dbe328599 ./core/status.sh 65f850342f43bdfe68b2b0339a051a795c6640b4bd4f7c6b6b979ca214607f9a87d9cfdc6955785c5ca087d09294781ee8cb3a62dc0160cd8a105ff65cc82465 ./themes/minterm.sh diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..024a679 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,84 @@ +# HyperTerm Tests + +Test suite for HyperTerm prompt functionality and performance. + +## Test Files + +### test_prompt.sh +Comprehensive test suite with full functionality testing. + +**Features:** +- Individual function testing +- Performance benchmarking +- Git state testing +- Interactive test mode (skipped in CI) +- Non-interactive mode for CI/automation + +**Usage:** +```bash +cd tests +bash test_prompt.sh # Interactive mode +bash test_prompt.sh --non-interactive # Non-interactive mode (for CI) +``` + +### quick_test.sh +Fast validation test for immediate feedback. + +**Features:** +- Quick function validation +- Basic performance check +- Works in current directory or creates temp repo + +**Usage:** +```bash +cd tests +bash quick_test.sh +``` + +## Test Environment + +Tests create isolated environments to avoid affecting your working directory: +- Temporary git repositories in `/tmp/` +- Various git states (clean, dirty, staged, untracked) +- Automatic cleanup on exit + +## Performance Testing + +Both test scripts include performance measurements: +- Multiple iterations for accurate timing +- Average execution time per prompt call +- Performance classification (Excellent < 10ms, Good < 50ms) + +## Git States Tested + +- Clean repository +- Modified files +- Untracked files +- Staged files +- Mixed states +- Ahead/behind tracking + +## Requirements + +- Git installed and available in PATH +- Bash 4.0 or later +- HyperTerm core files in `../hyperterm/core/` + +## Logging + +All tests use structured logging: +- `[INFO]` - General information +- `[SUCCESS]` - Successful operations +- `[WARN]` - Warnings +- `[ERROR]` - Errors + +## Interactive Mode + +The full test suite includes an interactive mode with commands: +- `status` - Show git status +- `prompt` - Show full prompt +- `perf` - Run performance test +- `states` - Test different git states +- `modify` - Create test modifications +- `clean` - Clean working directory +- `exit` - Exit interactive mode diff --git a/tests/quick_test.sh b/tests/quick_test.sh new file mode 100644 index 0000000..112796a --- /dev/null +++ b/tests/quick_test.sh @@ -0,0 +1,176 @@ +#!/bin/bash +# Quick Prompt Test - Fast validation +# Simple test for immediate feedback +# shellcheck disable=SC1090,SC2034,SC2155 + +set -e + +# Logging +log_info() { echo "[INFO] $*"; } +log_error() { echo "[ERROR] $*"; } +log_success() { echo "[SUCCESS] $*"; } + +# Configuration +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +# Basic colors fallback +setup_colors() { + RESET='\033[0m' + BOLD='\033[1m' + RED='\033[31m' + GREEN='\033[32m' + YELLOW='\033[33m' + BLUE='\033[34m' + CYAN='\033[36m' + WHITE='\033[37m' + GREY='\033[90m' +} + +# Load functions +load_functions() { + log_info "Loading HyperTerm functions" + + # Load colors if available + local colors_file="$PROJECT_ROOT/hyperterm/core/colors.sh" + if [[ -f "$colors_file" ]]; then + source "$colors_file" + else + setup_colors + fi + + # Load git functions + local git_file="$PROJECT_ROOT/hyperterm/core/git.sh" + + if [[ -f "$git_file" ]]; then + source "$git_file" + log_success "Git functions loaded" + return 0 + else + log_error "Git functions not found" + return 1 + fi +} + +# Test in current directory +test_current_directory() { + if git rev-parse --git-dir >/dev/null 2>&1; then + log_success "Current directory is a git repository" + + echo "Current git state:" + echo -n " Branch: " + _get_git_branch + echo "" + + echo -n " Status: " + if command -v _get_git_status_fast >/dev/null 2>&1; then + _get_git_status_fast + else + _prompt_get_git_status + fi + echo "" + + echo -n " Full prompt: " + __prompt_git + echo "" + + return 0 + else + log_info "Current directory is not a git repository" + return 1 + fi +} + +# Create temporary test +test_with_temp_repo() { + log_info "Creating temporary git repository for testing" + + local temp_dir="/tmp/quick_test_$$" + mkdir -p "$temp_dir" + + ( + cd "$temp_dir" + git init --quiet + git config user.name "Test User" + git config user.email "test@example.com" + echo "# Quick Test" > README.md + echo "test content" > file.txt + git add README.md + git commit -m "Initial commit" --quiet + + # Create changes + echo "modified" >> file.txt + echo "untracked" > new.txt + + log_success "Temporary repository created with changes" + + echo "Test results:" + echo -n " Branch: " + _get_git_branch + echo "" + + echo -n " Status: " + if command -v _get_git_status_fast >/dev/null 2>&1; then + _get_git_status_fast + else + _prompt_get_git_status + fi + echo "" + + echo -n " Full prompt: " + __prompt_git + echo "" + ) + + rm -rf "$temp_dir" + log_info "Temporary repository cleaned up" +} + +# Performance check +quick_performance_check() { + log_info "Running quick performance check" + + local iterations=10 + local start_time end_time duration + + start_time=$(date +%s%N) + for ((i=1; i<=iterations; i++)); do + __prompt_git >/dev/null 2>&1 + done + end_time=$(date +%s%N) + + duration=$(( (end_time - start_time) / 1000000 )) + local avg_duration=$(( duration / iterations )) + + echo "Performance: ${avg_duration}ms average (${iterations} iterations)" + + if [[ $avg_duration -lt 50 ]]; then + log_success "Performance is good" + elif [[ $avg_duration -lt 100 ]]; then + log_info "Performance is acceptable" + else + log_error "Performance may need optimization" + fi +} + +# Main execution +main() { + log_info "HyperTerm Quick Test" + + if ! load_functions; then + exit 1 + fi + + if ! test_current_directory; then + test_with_temp_repo + fi + + quick_performance_check + + log_success "Quick test completed" +} + +# Execute if run directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/tests/test_prompt.sh b/tests/test_prompt.sh new file mode 100644 index 0000000..b4a002b --- /dev/null +++ b/tests/test_prompt.sh @@ -0,0 +1,304 @@ +#!/bin/bash +# HyperTerm Prompt Test Suite +# Organized testing environment for prompt functionality +# shellcheck disable=SC1090,SC2034,SC2155 + +set -e + +# Logging functions +log_info() { echo "[INFO] $*"; } +log_warn() { echo "[WARN] $*"; } +log_error() { echo "[ERROR] $*"; } +log_success() { echo "[SUCCESS] $*"; } + +# Configuration +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +readonly TEST_DIR="/tmp/hyperterm_test_$(date +%s)" + +# Cleanup function +cleanup() { + if [[ -n "${TEST_DIR:-}" && -d "$TEST_DIR" ]]; then + rm -rf "$TEST_DIR" + log_info "Test directory cleaned: $TEST_DIR" + fi +} + +# Setup test environment +setup_test_env() { + log_info "Setting up test environment" + + mkdir -p "$TEST_DIR" + cd "$TEST_DIR" + + # Create test git repository + git init --quiet + git config user.name "Test User" + git config user.email "test@example.com" + echo "# Test Repository" > README.md + echo "test content" > test.txt + git add README.md + git commit -m "Initial commit" --quiet + + # Create various git states for testing + echo "modified content" >> test.txt + echo "untracked file" > untracked.txt + echo "staged content" > staged.txt + git add staged.txt + + log_success "Test environment created at: $TEST_DIR" + log_info "Git states created: modified, untracked, staged files" +} + +# Load hyperterm functions +load_hyperterm_functions() { + log_info "Loading HyperTerm functions" + + # Load colors + local colors_file="$PROJECT_ROOT/hyperterm/core/colors.sh" + if [[ -f "$colors_file" ]]; then + source "$colors_file" + log_success "Colors loaded from: $colors_file" + else + log_warn "Colors file not found, using fallback" + # Fallback colors + RESET='\033[0m' + BOLD='\033[1m' + RED='\033[31m' + GREEN='\033[32m' + YELLOW='\033[33m' + BLUE='\033[34m' + CYAN='\033[36m' + WHITE='\033[37m' + GREY='\033[90m' + fi + + # Load git functions + local git_file="$PROJECT_ROOT/hyperterm/core/git.sh" + + if [[ -f "$git_file" ]]; then + source "$git_file" + log_success "Git functions loaded" + return 0 + else + log_error "Git functions not found" + return 1 + fi +} + +# Test individual functions +test_individual_functions() { + log_info "Testing individual functions" + + echo "Branch detection:" + if command -v _get_git_branch >/dev/null 2>&1; then + local branch + branch="$(_get_git_branch)" + echo " Result: $branch" + log_success "Branch detection working" + else + log_error "Branch detection function not found" + fi + + echo "Git progress detection:" + if command -v _get_git_progress >/dev/null 2>&1; then + local progress + progress="$(_get_git_progress)" + echo " Result: ${progress:-"(none)"}" + log_success "Progress detection working" + else + log_error "Progress detection function not found" + fi + + echo "Git status:" + if command -v _get_git_status_fast >/dev/null 2>&1; then + local status + status="$(_get_git_status_fast)" + echo " Result: $status" + log_success "Fast git status working" + elif command -v _prompt_get_git_status >/dev/null 2>&1; then + local status + status="$(_prompt_get_git_status)" + echo " Result: $status" + log_success "Original git status working" + else + log_error "No git status function found" + fi +} + +# Performance benchmark +run_performance_test() { + log_info "Running performance benchmark" + + if ! command -v __prompt_git >/dev/null 2>&1; then + log_error "Prompt function not available" + return 1 + fi + + local iterations=50 + local start_time end_time duration + + log_info "Running $iterations iterations" + + start_time=$(date +%s%N) + for ((i=1; i<=iterations; i++)); do + __prompt_git >/dev/null 2>&1 + done + end_time=$(date +%s%N) + + duration=$(( (end_time - start_time) / 1000000 )) + local avg_duration=$(( duration / iterations )) + + echo "Performance Results:" + echo " Total time: ${duration}ms" + echo " Average per call: ${avg_duration}ms" + echo " Iterations: $iterations" + + if [[ $avg_duration -lt 10 ]]; then + log_success "Performance: Excellent (< 10ms per call)" + elif [[ $avg_duration -lt 50 ]]; then + log_success "Performance: Good (< 50ms per call)" + else + log_warn "Performance: Slow (>= 50ms per call)" + fi +} + +# Test different git states +test_git_states() { + log_info "Testing different git states" + + # Clean state + git checkout -- . >/dev/null 2>&1 + git clean -fd >/dev/null 2>&1 + echo "Clean repository:" + __prompt_git + echo "" + + # Modified files + echo "modified" >> test.txt + echo "Modified files:" + __prompt_git + echo "" + + # Untracked files + echo "untracked" > new_file.txt + echo "With untracked files:" + __prompt_git + echo "" + + # Staged files + git add new_file.txt + echo "With staged files:" + __prompt_git + echo "" + + log_success "Git states testing completed" +} + +# Interactive test mode +interactive_mode() { + log_info "Entering interactive test mode" + echo "Available commands:" + echo " status - Show current git status" + echo " prompt - Show full prompt" + echo " perf - Run performance test" + echo " states - Test different git states" + echo " modify - Modify files for testing" + echo " clean - Clean working directory" + echo " help - Show this help" + echo " exit - Exit interactive mode" + echo "" + + while true; do + echo -n "test> " + read -r command + + case "$command" in + "status") + if command -v _get_git_status_fast >/dev/null 2>&1; then + _get_git_status_fast + else + _prompt_get_git_status + fi + echo "" + ;; + "prompt") + __prompt_git + echo "" + ;; + "perf") + run_performance_test + ;; + "states") + test_git_states + ;; + "modify") + echo "Creating test modifications" + echo "change $(date)" >> test.txt + echo "new file $(date)" > "file_$(date +%s).txt" + log_success "Files modified" + ;; + "clean") + git checkout -- . >/dev/null 2>&1 + git clean -fd >/dev/null 2>&1 + log_success "Working directory cleaned" + ;; + "help") + echo "Available commands: status, prompt, perf, states, modify, clean, help, exit" + ;; + "exit") + log_info "Exiting interactive mode" + break + ;; + "") + # Empty command, continue + ;; + *) + log_warn "Unknown command: $command (type 'help' for available commands)" + ;; + esac + done +} + +# Main execution +main() { + log_info "Starting HyperTerm Prompt Test Suite" + + # Setup + setup_test_env + + if ! load_hyperterm_functions; then + log_error "Failed to load HyperTerm functions" + exit 1 + fi + + # Run tests + test_individual_functions + echo "" + run_performance_test + echo "" + test_git_states + echo "" + + # Interactive mode (only if not in CI or non-interactive mode) + if [[ "$1" != "--non-interactive" && "$1" != "-n" && -z "${CI:-}" ]]; then + log_info "All automated tests completed" + echo "Enter interactive mode? (y/N)" + read -r response + if [[ "$response" =~ ^[Yy]$ ]]; then + interactive_mode + fi + else + log_info "Running in non-interactive mode, skipping interactive prompt" + fi + + log_success "Test suite completed successfully" +} + +# Trap cleanup on exit +trap cleanup EXIT + +# Execute main function if script is run directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi -- cgit v1.2.3