Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ copilot-cli 1.0.69
squad-cli 0.10.0
gh 2.92.0
delta 0.19.2
lazygit 0.63.0
81 changes: 81 additions & 0 deletions scripts/linux/tools/lazygit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
# scripts/linux/tools/lazygit.sh -- Install lazygit (terminal UI for git) at pinned version
#
# Called by: scripts/linux/setup.sh
# Idempotent: yes -- version-aware; upgrades if installed version != pinned version.
# Opt-in: NOT in DEFAULT_TOOLS; only runs when requested via --only=lazygit.
#
# macOS: brew install lazygit (warns if brew version differs from pin).
# Linux: downloads the pinned release tarball from GitHub releases.
# Asset naming: lazygit_<version>_linux_x86_64.tar.gz (no leading v; lowercase os/arch).
# Binary is at the archive root -- extract and install to ~/.local/bin.
# Note: Ubuntu PPA lags significantly; tarball is primary for version accuracy.

# shellcheck disable=SC1091
. "$(dirname "${BASH_SOURCE[0]}")/../lib/log.sh"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LG_VERSION="$(sh "${SCRIPT_DIR}/../../lib/read-tool-version.sh" lazygit)"

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
set -euo pipefail

# Detect installed version
INSTALLED_VERSION=""
if command -v lazygit &>/dev/null; then
INSTALLED_VERSION="$(lazygit --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)"
fi

if [ "${INSTALLED_VERSION}" = "${LG_VERSION}" ]; then
log_ok "lazygit already at pinned version ${LG_VERSION}"
exit 0
fi

if [ -n "${INSTALLED_VERSION}" ]; then
log_info "lazygit ${INSTALLED_VERSION} installed; upgrading to pinned ${LG_VERSION}..."
else
log_info "Installing lazygit ${LG_VERSION}..."
fi

PLATFORM="$(uname -s)"
if [[ "$PLATFORM" == "Darwin" ]]; then
# Homebrew: versioned formulae for lazygit are not reliably pinnable.
if command -v lazygit &>/dev/null; then
brew upgrade lazygit || true
else
brew install lazygit
fi
ACTUAL_VERSION="$(lazygit --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo 'unknown')"
if [ "${ACTUAL_VERSION}" != "${LG_VERSION}" ]; then
log_warn "lazygit ${ACTUAL_VERSION} installed (pinned: ${LG_VERSION}); brew cannot guarantee exact version"
else
log_ok "lazygit installed at ${LG_VERSION}"
fi
else
# Linux: tarball from GitHub releases (asset names use lowercase os/arch, no leading v).
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) ARCH_SUFFIX="x86_64" ;;
aarch64|arm64) ARCH_SUFFIX="arm64" ;;
*) log_error "Unsupported architecture: ${ARCH}"; exit 1 ;;
esac

TARBALL="lazygit_${LG_VERSION}_linux_${ARCH_SUFFIX}.tar.gz"
TARBALL_URL="https://github.com/jesseduffield/lazygit/releases/download/v${LG_VERSION}/${TARBALL}"
INSTALL_DIR="${HOME}/.local/bin"
WORK_DIR="${HOME}/.local/share/dev-setup-install/lazygit"

mkdir -p "$INSTALL_DIR"
mkdir -p "$WORK_DIR"

log_info "Downloading ${TARBALL}..."
curl -fsSL "$TARBALL_URL" -o "${WORK_DIR}/${TARBALL}"
# Binary is at the archive root (no version-named subdirectory).
tar -xzf "${WORK_DIR}/${TARBALL}" -C "$WORK_DIR"
cp "${WORK_DIR}/lazygit" "${INSTALL_DIR}/lazygit"
chmod +x "${INSTALL_DIR}/lazygit"
rm -rf "$WORK_DIR"

log_ok "lazygit ${LG_VERSION} installed to ${INSTALL_DIR}/lazygit"
fi
fi
2 changes: 2 additions & 0 deletions scripts/windows/setup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ $ErrorActionPreference = 'Stop'
. "$PSScriptRoot\tools\auth.ps1"
. "$PSScriptRoot\tools\git-hook.ps1"
. "$PSScriptRoot\tools\delta.ps1"
. "$PSScriptRoot\tools\lazygit.ps1"

# ---------------------------------------------------------------------------
# $DefaultTools -- single ordered source of truth for a no-arg default run.
Expand Down Expand Up @@ -87,6 +88,7 @@ $ToolRegistry = [ordered]@{
'profile' = { Write-PowerShellProfile }
'git-hook' = { Install-GitHook }
'delta' = { Install-Delta }
'lazygit' = { Install-Lazygit }
}

# ---------------------------------------------------------------------------
Expand Down
53 changes: 53 additions & 0 deletions scripts/windows/tools/lazygit.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# scripts/windows/tools/lazygit.ps1 - lazygit installer
#
# Installs lazygit at pinned version from .tool-versions.
# Opt-in: NOT in $DefaultTools; only runs when requested via -Only 'lazygit'.
# winget preferred (id JesseDuffield.lazygit); scoop fallback when winget unavailable.
# PS 5.1 ASCII-only: no smart quotes, em-dashes, or non-ASCII characters.

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

. "$PSScriptRoot\..\lib\logging.ps1"
. "$PSScriptRoot\..\lib\path.ps1"
. "$PSScriptRoot\..\..\lib\Read-ToolVersion.ps1"

function Install-Lazygit {
$LgVersion = Get-ToolVersion -Name 'lazygit'

# Detect installed version
$InstalledVersion = ''
if (Get-Command lazygit -ErrorAction SilentlyContinue) {
$raw = (lazygit --version 2>&1) | Select-Object -First 1 | Out-String
$m = [regex]::Match($raw, '[0-9]+\.[0-9]+\.[0-9]+')
if ($m.Success) { $InstalledVersion = $m.Value }
}

if ($InstalledVersion -eq $LgVersion) {
Write-Ok "lazygit already at pinned version $LgVersion"
return
}

if ($InstalledVersion) {
Write-Info "lazygit $InstalledVersion installed; upgrading to pinned $LgVersion..."
} else {
Write-Info "Installing lazygit $LgVersion..."
}

# winget preferred; fall back to scoop if winget is unavailable
if (Get-Command winget -ErrorAction SilentlyContinue) {
winget install --id JesseDuffield.lazygit --version $LgVersion --silent `
--accept-source-agreements --accept-package-agreements
Assert-LastExit -ToolName "lazygit" -AllowedExitCodes @(0, -1978335189)
Refresh-SessionPath
Write-Ok "lazygit installed via winget at $LgVersion"
} elseif (Get-Command scoop -ErrorAction SilentlyContinue) {
Write-Info "winget not available; falling back to scoop..."
scoop install lazygit
Assert-LastExit -ToolName "lazygit (scoop)"
Write-Warn "scoop installed latest lazygit; version may differ from pinned $LgVersion"
} else {
Write-Err "Neither winget nor scoop available; cannot install lazygit"
throw "lazygit install failed: no supported package manager found"
}
}
2 changes: 2 additions & 0 deletions tests/fixtures/stub-tools/linux/lazygit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
echo "lazygit" >> "$RUN_LOG"
4 changes: 4 additions & 0 deletions tests/fixtures/stub-tools/windows/lazygit.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# stub: lazygit
param()
$n = 'lazygit'
if ($env:RUN_LOG) { Add-Content -Path $env:RUN_LOG -Value $n }
109 changes: 109 additions & 0 deletions tests/test_lazygit_installer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# tests/test_lazygit_installer.sh -- parity tests for lazygit opt-in installer (#467)
#
# Tests:
# T_lazygit_in_list -- lazygit appears in --list output (opt-in discoverable)
# T_lazygit_not_default -- lazygit is NOT installed by a default no-arg run
# T_lazygit_version_pin -- .tool-versions contains a lazygit pin
# T_lazygit_optin_stub -- --only=lazygit with stub dir runs only lazygit
#
# Usage: bash tests/test_lazygit_installer.sh
# Requires: bash 3.2+ (macOS compatible)

set -uo pipefail

PASS=0
FAIL=0
SKIP=0
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RESET='\033[0m'

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
LINUX_SETUP="${REPO_ROOT}/scripts/linux/setup.sh"
TOOL_VERSIONS="${REPO_ROOT}/.tool-versions"
STUB_DIR="${REPO_ROOT}/tests/fixtures/stub-tools/linux"

pass() { printf "${GREEN}PASS${RESET}: %s\n" "$1"; PASS=$((PASS + 1)); }
fail() { printf "${RED}FAIL${RESET}: %s\n" "$1"; FAIL=$((FAIL + 1)); }
# shellcheck disable=SC2329
skip() { printf "${YELLOW}SKIP${RESET}: %s -- %s\n" "$1" "$2"; SKIP=$((SKIP + 1)); }

# ---------------------------------------------------------------------------
# T_lazygit_in_list: --list on real tools dir includes 'lazygit'
# Fails RED when scripts/linux/tools/lazygit.sh does not exist.
# ---------------------------------------------------------------------------
echo ""
echo "--- T_lazygit_in_list ---"
list_out="$(bash "$LINUX_SETUP" --list 2>&1)" && list_exit=$? || list_exit=$?
if [ "$list_exit" -ne 0 ]; then
fail "T_lazygit_in_list: --list exited $list_exit (expected 0)"
elif echo "$list_out" | grep -qF "lazygit"; then
pass "T_lazygit_in_list: lazygit appears in --list output (opt-in discoverable)"
else
fail "T_lazygit_in_list: lazygit missing from --list output (is lazygit.sh in tools/?)"
echo " --list output: $list_out"
fi

# ---------------------------------------------------------------------------
# T_lazygit_not_default: default no-arg run with stub dir does NOT run lazygit
# lazygit is opt-in; it must not appear in defaults.txt.
# ---------------------------------------------------------------------------
echo ""
echo "--- T_lazygit_not_default ---"
RUN_LOG="$(mktemp)"
export RUN_LOG
bash "$LINUX_SETUP" "--tools-dir=${STUB_DIR}" >/dev/null 2>&1 || true
if grep -qF "lazygit" "$RUN_LOG" 2>/dev/null; then
fail "T_lazygit_not_default: lazygit ran in a default no-arg install (must be opt-in only)"
else
pass "T_lazygit_not_default: lazygit does NOT run in a default install (correctly opt-in)"
fi
rm -f "$RUN_LOG"
unset RUN_LOG

# ---------------------------------------------------------------------------
# T_lazygit_version_pin: .tool-versions contains a lazygit entry
# Fails RED before lazygit is added to .tool-versions.
# ---------------------------------------------------------------------------
echo ""
echo "--- T_lazygit_version_pin ---"
if grep -qE '^lazygit[[:space:]]' "$TOOL_VERSIONS" 2>/dev/null; then
ver="$(grep -E '^lazygit[[:space:]]' "$TOOL_VERSIONS" | awk '{print $2}')"
pass "T_lazygit_version_pin: lazygit pinned at ${ver} in .tool-versions"
else
fail "T_lazygit_version_pin: lazygit not found in .tool-versions -- RED (pre-implementation)"
fi

# ---------------------------------------------------------------------------
# T_lazygit_optin_stub: --only=lazygit with stub dir runs only lazygit
# Fails RED when tests/fixtures/stub-tools/linux/lazygit.sh does not exist.
# ---------------------------------------------------------------------------
echo ""
echo "--- T_lazygit_optin_stub ---"
RUN_LOG2="$(mktemp)"
export RUN_LOG
RUN_LOG="$RUN_LOG2"
bash "$LINUX_SETUP" "--tools-dir=${STUB_DIR}" --only=lazygit 2>&1 | grep -q . || true
if grep -qF "lazygit" "$RUN_LOG2" 2>/dev/null; then
actual="$(cat "$RUN_LOG2")"
if [ "$actual" = "lazygit" ]; then
pass "T_lazygit_optin_stub: --only=lazygit runs only lazygit via stub"
else
fail "T_lazygit_optin_stub: --only=lazygit ran unexpected tools: ${actual}"
fi
else
fail "T_lazygit_optin_stub: --only=lazygit did not run lazygit (stub missing or tool unregistered)"
fi
rm -f "$RUN_LOG2"
unset RUN_LOG

# ---------------------------------------------------------------------------
echo ""
echo "Results: ${PASS} passed, ${FAIL} failed, ${SKIP} skipped"
if [ "$FAIL" -gt 0 ]; then
exit 1
fi
exit 0
116 changes: 116 additions & 0 deletions tests/test_lazygit_installer_pwsh.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# tests/test_lazygit_installer_pwsh.ps1 -- parity tests for lazygit opt-in installer (#467)
#
# Tests:
# T_lazygit_in_list -- lazygit appears in -List output (opt-in discoverable)
# T_lazygit_not_default -- lazygit is NOT installed by a default no-arg run
# T_lazygit_version_pin -- .tool-versions contains a lazygit pin
# T_lazygit_optin_stub -- -Only 'lazygit' with stub dir runs only lazygit
#
# Usage: powershell -ExecutionPolicy Bypass -File tests\test_lazygit_installer_pwsh.ps1
# PS 5.1 ASCII-only: no smart quotes, em-dashes, or non-ASCII characters.

$ErrorActionPreference = 'Stop'
$TestsPassed = 0
$TestsFailed = 0
$TestsSkipped = 0

$RepoRoot = Split-Path $PSScriptRoot -Parent
$WinSetup = Join-Path $RepoRoot 'scripts\windows\setup.ps1'
$StubDir = Join-Path $RepoRoot 'tests\fixtures\stub-tools\windows'
$ToolVersions = Join-Path $RepoRoot '.tool-versions'

function Test-Scenario {
param([string]$Name, [scriptblock]$Test)
Write-Host "`n=== TEST: $Name ===" -ForegroundColor Cyan
try {
& $Test
Write-Host "[PASS] $Name" -ForegroundColor Green
$script:TestsPassed++
}
catch {
Write-Host "[FAIL] $Name" -ForegroundColor Red
Write-Host " Error: $_" -ForegroundColor Red
$script:TestsFailed++
}
}

# ---------------------------------------------------------------------------
# T_lazygit_in_list: -List on real registry includes 'lazygit'
# Fails RED when lazygit.ps1 is not dot-sourced + registered in setup.ps1.
# ---------------------------------------------------------------------------

Test-Scenario "T_lazygit_in_list: lazygit appears in -List output (opt-in discoverable)" {
$out = powershell -NoProfile -ExecutionPolicy Bypass -File $WinSetup -List 2>&1 | Out-String
$ec = $LASTEXITCODE
if ($ec -ne 0) { throw "-List exited $ec (expected 0)" }
if ($out -notmatch '\blazygit\b') {
throw "lazygit missing from -List output; has lazygit.ps1 been registered in ToolRegistry?"
}
}

# ---------------------------------------------------------------------------
# T_lazygit_not_default: default no-arg run with stub dir does NOT run lazygit
# ---------------------------------------------------------------------------

Test-Scenario "T_lazygit_not_default: lazygit does NOT run in a default install (opt-in only)" {
$runLog = [System.IO.Path]::GetTempFileName()
$env:RUN_LOG = $runLog
try {
powershell -NoProfile -ExecutionPolicy Bypass -File $WinSetup `
-ToolsDir $StubDir 2>&1 | Out-Null
$content = Get-Content $runLog -ErrorAction SilentlyContinue
if ($content -contains 'lazygit') {
throw "lazygit ran in a default no-arg install (must be opt-in only)"
}
}
finally {
$env:RUN_LOG = $null
Remove-Item $runLog -ErrorAction SilentlyContinue
}
}

# ---------------------------------------------------------------------------
# T_lazygit_version_pin: .tool-versions contains a lazygit entry
# Fails RED before lazygit is added to .tool-versions.
# ---------------------------------------------------------------------------

Test-Scenario "T_lazygit_version_pin: lazygit is pinned in .tool-versions" {
$lines = Get-Content $ToolVersions
$entry = $lines | Where-Object { $_ -match '^lazygit\s+' }
if (-not $entry) {
throw "lazygit not found in .tool-versions -- RED (pre-implementation)"
}
$ver = ($entry -split '\s+')[1]
Write-Host " lazygit pinned at $ver" -ForegroundColor DarkGray
}

# ---------------------------------------------------------------------------
# T_lazygit_optin_stub: -Only 'lazygit' with stub dir runs only lazygit
# Fails RED when lazygit.ps1 stub is not in the stub dir OR not in ToolRegistry.
# ---------------------------------------------------------------------------

Test-Scenario "T_lazygit_optin_stub: -Only 'lazygit' runs only lazygit via stub" {
$runLog = [System.IO.Path]::GetTempFileName()
$env:RUN_LOG = $runLog
try {
powershell -NoProfile -ExecutionPolicy Bypass -File $WinSetup `
-ToolsDir $StubDir -Only 'lazygit' 2>&1 | Out-Null
$content = Get-Content $runLog -ErrorAction SilentlyContinue
if (-not $content) { throw "-Only 'lazygit' produced empty run-log" }
if ($content -notcontains 'lazygit') {
throw "-Only 'lazygit' did not run lazygit stub; log: $($content -join ', ')"
}
$extra = $content | Where-Object { $_ -ne 'lazygit' }
if ($extra) { throw "-Only 'lazygit' ran unexpected tools: $($extra -join ', ')" }
}
finally {
$env:RUN_LOG = $null
Remove-Item $runLog -ErrorAction SilentlyContinue
}
}

# ---------------------------------------------------------------------------
Write-Host ""
Write-Host "Results: $TestsPassed passed, $TestsFailed failed, $TestsSkipped skipped"
if ($TestsFailed -gt 0) { exit 1 }
exit 0
Loading