mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-07 00:37:36 +00:00
532 lines
18 KiB
Bash
Executable file
532 lines
18 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ── Overnight Lint Fixer — errcheck + dupl ─────────────────────────────
|
|
#
|
|
# Fixes golangci-lint errcheck and dupl warnings using OpenCode + MiniMax M2.5.
|
|
# OpenCode provides autonomous code editing with built-in verification.
|
|
#
|
|
# Usage:
|
|
# ./scripts/lint-fixer/run.sh
|
|
#
|
|
# Options:
|
|
# --model MODEL OpenCode model (default: opencode/minimax-m2.5-free)
|
|
# --lint LINTERS Comma-separated linters to fix (default: errcheck,dupl)
|
|
# --packages PKGS Comma-separated packages to target (default: all with warnings)
|
|
# --max-iters N Max iterations (default: 100)
|
|
# --branch NAME Branch name (default: lint-fixes/TIMESTAMP, use "current" for current branch)
|
|
# --no-worktree Work directly on current branch (DANGEROUS — no isolation)
|
|
#
|
|
# Overnight execution:
|
|
# nohup ./scripts/lint-fixer/run.sh > /tmp/lint-fixer.log 2>&1 &
|
|
#
|
|
# Graceful shutdown:
|
|
# touch scripts/lint-fixer/.stop
|
|
#
|
|
# Monitoring:
|
|
# ./scripts/lint-fixer/watch.sh
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
WORKTREE_BASE="$REPO_DIR/../pulse-lint-fixes"
|
|
|
|
# Survive terminal disconnection
|
|
trap '' HUP
|
|
|
|
# Defaults
|
|
MODEL="opencode/minimax-m2.5-free"
|
|
LINTERS="errcheck,dupl"
|
|
PACKAGES=""
|
|
MAX_ITERS=100
|
|
BRANCH=""
|
|
USE_WORKTREE=false # Default to no worktree (simpler, avoids build issues)
|
|
AIDER_TIMEOUT_SECS="${AIDER_TIMEOUT_SECS:-900}"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--model) MODEL="$2"
|
|
# Normalize model name to include openrouter/ prefix.
|
|
if [[ "$MODEL" == minimax/* ]] || [[ "$MODEL" == deepseek/* ]]; then
|
|
MODEL="openrouter/$MODEL"
|
|
fi
|
|
shift 2 ;;
|
|
--lint) LINTERS="$2"; shift 2 ;;
|
|
--packages) PACKAGES="$2"; shift 2 ;;
|
|
--max-iters) MAX_ITERS="$2"; shift 2 ;;
|
|
--branch) BRANCH="$2"; shift 2 ;;
|
|
--worktree) USE_WORKTREE=true; shift ;;
|
|
--no-worktree) USE_WORKTREE=false; shift ;;
|
|
--engine) ENGINE="$2"; shift 2 ;;
|
|
*) echo "Unknown arg: $1"; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# Validate environment
|
|
if ! command -v opencode >/dev/null 2>&1; then
|
|
echo "Error: opencode not found"
|
|
echo " Install with: brew install opencode"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v golangci-lint >/dev/null 2>&1; then
|
|
GOLANGCI_LINT="$HOME/go/bin/golangci-lint"
|
|
if [ ! -f "$GOLANGCI_LINT" ]; then
|
|
echo "Error: golangci-lint not found in PATH or ~/go/bin/"
|
|
exit 1
|
|
fi
|
|
else
|
|
GOLANGCI_LINT="golangci-lint"
|
|
fi
|
|
|
|
cd "$REPO_DIR"
|
|
|
|
# Generate branch name if not provided
|
|
if [ -z "$BRANCH" ]; then
|
|
BRANCH="lint-fixes/$(date +%Y%m%d-%H%M%S)"
|
|
fi
|
|
|
|
# Special handling: if branch is "current", use the current branch
|
|
if [ "$BRANCH" = "current" ]; then
|
|
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
fi
|
|
|
|
STOP_FILE="$SCRIPT_DIR/.stop"
|
|
PROGRESS_FILE="$SCRIPT_DIR/PROGRESS.md"
|
|
LOG_FILE="$SCRIPT_DIR/run.log"
|
|
|
|
rm -f "$STOP_FILE"
|
|
mkdir -p "$(dirname "$LOG_FILE")"
|
|
|
|
should_stop() {
|
|
[ -f "$STOP_FILE" ]
|
|
}
|
|
|
|
# Run a command with a hard timeout (bash 3 compatible).
|
|
run_with_timeout() {
|
|
local timeout_secs="$1"
|
|
shift
|
|
|
|
"$@" &
|
|
local cmd_pid=$!
|
|
local start_time
|
|
start_time=$(date +%s)
|
|
|
|
while kill -0 "$cmd_pid" 2>/dev/null; do
|
|
if [ $(( $(date +%s) - start_time )) -ge "$timeout_secs" ]; then
|
|
kill "$cmd_pid" 2>/dev/null || true
|
|
sleep 2
|
|
kill -9 "$cmd_pid" 2>/dev/null || true
|
|
wait "$cmd_pid" 2>/dev/null || true
|
|
return 124
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
wait "$cmd_pid"
|
|
}
|
|
|
|
STARTED_AT=$(date +%s)
|
|
|
|
echo "╔══════════════════════════════════════════════════════╗"
|
|
echo "║ Pulse Overnight Lint Fixer ║"
|
|
echo "╠══════════════════════════════════════════════════════╣"
|
|
echo "║ Model: $MODEL"
|
|
echo "║ Linters: $LINTERS"
|
|
echo "║ Branch: $BRANCH"
|
|
echo "║ Max iters: $MAX_ITERS"
|
|
echo "║ Started: $(date)"
|
|
echo "╚══════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
# Set up worktree or work in-place
|
|
if [ "$USE_WORKTREE" = true ]; then
|
|
WORK_DIR="$WORKTREE_BASE"
|
|
|
|
if [ -d "$WORK_DIR" ]; then
|
|
git worktree remove --force "$WORK_DIR" 2>/dev/null || rm -rf "$WORK_DIR"
|
|
fi
|
|
|
|
BASE_SHA=$(git rev-parse HEAD)
|
|
git worktree add -b "$BRANCH" "$WORK_DIR" "$BASE_SHA" 2>/dev/null
|
|
|
|
echo "Working in isolated worktree: $WORK_DIR"
|
|
echo "Base commit: $BASE_SHA"
|
|
echo ""
|
|
|
|
cd "$WORK_DIR"
|
|
|
|
# Copy frontend dist if it exists (required for embed directives)
|
|
if [ -d "$REPO_DIR/frontend-modern/dist" ]; then
|
|
echo "Copying frontend-modern/dist for Go build..."
|
|
cp -r "$REPO_DIR/frontend-modern/dist" frontend-modern/
|
|
fi
|
|
|
|
# Build internal/ packages to populate build cache
|
|
echo "Building internal/ packages..."
|
|
if go build ./internal/... >> "$LOG_FILE" 2>&1; then
|
|
echo "✓ Build successful"
|
|
else
|
|
echo "Warning: go build failed, some linters may not work correctly"
|
|
fi
|
|
echo ""
|
|
else
|
|
WORK_DIR="$REPO_DIR"
|
|
BASE_SHA=$(git rev-parse HEAD)
|
|
|
|
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
if [ "$CURRENT_BRANCH" != "$BRANCH" ]; then
|
|
git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH"
|
|
echo "Working directly in repo on branch: $BRANCH"
|
|
else
|
|
echo "Working directly in repo on current branch: $BRANCH"
|
|
fi
|
|
echo "Base commit: $BASE_SHA"
|
|
echo ""
|
|
fi
|
|
|
|
# Initialize progress file
|
|
cat > "$PROGRESS_FILE" << 'PROGRESS_EOF'
|
|
# Lint Fixer Progress
|
|
|
|
## Status
|
|
- **Started**: (timestamp)
|
|
- **Packages completed**: 0
|
|
- **Commits**: 0
|
|
- **Warnings fixed**: 0
|
|
|
|
## Package Log
|
|
(will be populated as we go)
|
|
PROGRESS_EOF
|
|
|
|
sed -i.bak "s/(timestamp)/$(date)/" "$PROGRESS_FILE" && rm -f "$PROGRESS_FILE.bak"
|
|
|
|
# ── Get list of packages with warnings ────────────────────────────────
|
|
|
|
echo "Scanning for lint warnings..."
|
|
|
|
# Parse LINTERS into array
|
|
IFS=',' read -ra LINT_ARRAY <<< "$LINTERS"
|
|
|
|
# Parse requested packages (optional)
|
|
REQUESTED_PACKAGES=()
|
|
if [ -n "$PACKAGES" ]; then
|
|
IFS=',' read -ra RAW_PACKAGES <<< "$PACKAGES"
|
|
for raw_pkg in "${RAW_PACKAGES[@]}"; do
|
|
raw_pkg="$(echo "$raw_pkg" | xargs)"
|
|
[ -n "$raw_pkg" ] || continue
|
|
raw_pkg="${raw_pkg#./}"
|
|
raw_pkg="${raw_pkg#internal/}"
|
|
raw_pkg="${raw_pkg%/...}"
|
|
raw_pkg="${raw_pkg%/}"
|
|
[ -n "$raw_pkg" ] || continue
|
|
REQUESTED_PACKAGES+=("$raw_pkg")
|
|
done
|
|
fi
|
|
|
|
SCAN_TARGETS=()
|
|
if [ ${#REQUESTED_PACKAGES[@]} -gt 0 ]; then
|
|
for pkg in "${REQUESTED_PACKAGES[@]}"; do
|
|
SCAN_TARGETS+=("./internal/$pkg/...")
|
|
done
|
|
else
|
|
SCAN_TARGETS+=("./internal/...")
|
|
fi
|
|
|
|
# Build package list (bash 3 compatible: avoid associative arrays)
|
|
PKG_WARNING_TMP=$(mktemp -t lint-fixer-pkg-warnings.XXXXXX)
|
|
for linter in "${LINT_ARRAY[@]}"; do
|
|
for target in "${SCAN_TARGETS[@]}"; do
|
|
while IFS= read -r line; do
|
|
if [[ "$line" =~ ^internal/([^/]+)/ ]]; then
|
|
pkg="${BASH_REMATCH[1]}"
|
|
echo "$pkg" >> "$PKG_WARNING_TMP"
|
|
fi
|
|
done < <($GOLANGCI_LINT run --enable "$linter" --disable-all "$target" 2>&1 | grep "$linter" || true)
|
|
done
|
|
done
|
|
|
|
# Convert to sorted array (by warning count ascending for quick wins first)
|
|
PACKAGES_TO_FIX=()
|
|
PACKAGE_WARNING_COUNTS=()
|
|
if [ -s "$PKG_WARNING_TMP" ]; then
|
|
while read -r count pkg; do
|
|
[ -n "$pkg" ] || continue
|
|
PACKAGES_TO_FIX+=("$pkg")
|
|
PACKAGE_WARNING_COUNTS+=("$count")
|
|
done < <(sort "$PKG_WARNING_TMP" | uniq -c | awk '{print $1 " " $2}' | sort -n)
|
|
fi
|
|
rm -f "$PKG_WARNING_TMP"
|
|
|
|
if [ ${#PACKAGES_TO_FIX[@]} -eq 0 ]; then
|
|
echo "No packages with warnings found!"
|
|
exit 0
|
|
fi
|
|
|
|
echo "Found ${#PACKAGES_TO_FIX[@]} packages with warnings:"
|
|
for idx in "${!PACKAGES_TO_FIX[@]}"; do
|
|
pkg="${PACKAGES_TO_FIX[$idx]}"
|
|
pkg_warning_count="${PACKAGE_WARNING_COUNTS[$idx]}"
|
|
echo " internal/$pkg: ${pkg_warning_count} warnings"
|
|
done
|
|
echo ""
|
|
|
|
# ── Main loop: fix packages one by one ────────────────────────────────
|
|
|
|
TOTAL_COMMITS=0
|
|
TOTAL_WARNINGS_FIXED=0
|
|
PACKAGES_COMPLETED=0
|
|
|
|
for idx in "${!PACKAGES_TO_FIX[@]}"; do
|
|
pkg="${PACKAGES_TO_FIX[$idx]}"
|
|
pkg_warning_count="${PACKAGE_WARNING_COUNTS[$idx]}"
|
|
if should_stop; then
|
|
echo "Stop signal received — shutting down gracefully"
|
|
break
|
|
fi
|
|
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "Package: internal/$pkg (${pkg_warning_count} warnings)"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
|
|
PKG_START=$(date +%s)
|
|
BEFORE_SHA=$(git rev-parse HEAD)
|
|
|
|
for linter in "${LINT_ARRAY[@]}"; do
|
|
if should_stop; then break; fi
|
|
|
|
echo "→ Running $linter on internal/$pkg..."
|
|
|
|
# Get warnings for this package + linter
|
|
WARNINGS=$($GOLANGCI_LINT run --enable "$linter" --disable-all "./internal/$pkg/..." 2>&1 | grep "$linter" || true)
|
|
|
|
if [ -z "$WARNINGS" ]; then
|
|
echo " ✓ No $linter warnings"
|
|
continue
|
|
fi
|
|
|
|
WARNING_COUNT=$(echo "$WARNINGS" | wc -l | tr -d ' ')
|
|
echo " Found $WARNING_COUNT $linter warnings"
|
|
|
|
# Extract affected files and process one file at a time to avoid model context overflow.
|
|
FILES=$(echo "$WARNINGS" | awk -F: '/^[^:]+\.go:/{print $1}' | sort -u || true)
|
|
if [ -z "$FILES" ]; then
|
|
echo " ⚠ Could not extract file list from warnings"
|
|
continue
|
|
fi
|
|
|
|
FILE_COUNT=$(echo "$FILES" | wc -l | tr -d ' ')
|
|
echo " Affected files: $FILE_COUNT"
|
|
|
|
while IFS= read -r file; do
|
|
if should_stop; then break; fi
|
|
[ -n "$file" ] || continue
|
|
|
|
if [ ! -f "$file" ]; then
|
|
echo " ⚠ Skipping missing file: $file"
|
|
continue
|
|
fi
|
|
|
|
FILE_WARNINGS=$(echo "$WARNINGS" | awk -v f="$file" 'index($0, f ":") == 1')
|
|
if [ -z "$FILE_WARNINGS" ]; then
|
|
continue
|
|
fi
|
|
FILE_WARNING_COUNT=$(echo "$FILE_WARNINGS" | wc -l | tr -d ' ')
|
|
echo " → File: $file ($FILE_WARNING_COUNT warnings)"
|
|
|
|
# Build MiniMax M2.5 optimized prompt (purpose-driven, explain "why")
|
|
case "$linter" in
|
|
errcheck)
|
|
PROMPT="## Task: Fix errcheck warnings in $file (internal/$pkg)
|
|
|
|
**Why this matters:** Unchecked errors can cause silent failures, data corruption, or security issues in production. We need proper error handling to make Pulse reliable for users monitoring critical infrastructure.
|
|
|
|
**Context:** This is production code for Pulse, an infrastructure monitoring platform. Users depend on accurate metrics and reliable operations. Every unchecked error is a potential production incident.
|
|
|
|
**Warnings to fix (this file only):**
|
|
\`\`\`
|
|
$FILE_WARNINGS
|
|
\`\`\`
|
|
|
|
**How to fix them:**
|
|
|
|
For **test files** (\`*_test.go\`):
|
|
- If the error is truly irrelevant (e.g., \`defer file.Close()\` in test setup), use: \`_ = expr\`
|
|
- Example: \`_ = os.Remove(tmpFile)\` when cleanup failure doesn't affect test validity
|
|
|
|
For **production code**:
|
|
- If function returns \`error\`: propagate it
|
|
- Good: \`return fmt.Errorf(\"failed to save config: %w\", err)\`
|
|
- Bad: ignoring the error
|
|
- If function doesn't return error but should handle it: log and continue gracefully
|
|
- Example: \`if err := conn.Close(); err != nil { log.Warn(\"connection close failed: %v\", err) }\`
|
|
- For critical operations (database writes, API calls, file I/O): ALWAYS handle errors
|
|
|
|
**Constraints:**
|
|
- Do NOT change function signatures
|
|
- Do NOT add new imports unless absolutely necessary
|
|
- Keep changes minimal — fix only the error handling for this file
|
|
- Preserve all existing behavior
|
|
- Ensure all tests still pass
|
|
|
|
**Expected output:** Clean, production-ready code with all errors properly handled."
|
|
;;
|
|
|
|
dupl)
|
|
PROMPT="## Task: Eliminate code duplication tied to warnings in $file (internal/$pkg)
|
|
|
|
**Why this matters:** Duplicated code creates maintenance burden. When we fix a bug or add a feature, we have to find and update multiple copies, which leads to inconsistencies and missed updates. DRY code is easier to maintain and less error-prone.
|
|
|
|
**Context:** This is production code for Pulse. We're refactoring to improve long-term maintainability without changing any behavior.
|
|
|
|
**Duplication warnings (anchored to this file):**
|
|
\`\`\`
|
|
$FILE_WARNINGS
|
|
\`\`\`
|
|
|
|
**How to fix them:**
|
|
|
|
1. **Identify** the duplicated logic blocks
|
|
2. **Extract** into well-named helper functions:
|
|
- Use descriptive names that explain what they do (e.g., \`buildProxmoxMetricPayload\`, \`formatTimestampISO8601\`)
|
|
- Keep helpers private (lowercase) unless they need to be exported
|
|
- Place helpers near their call sites (same file if only used there, separate file if shared across package)
|
|
- Add brief comment explaining purpose if not obvious from name
|
|
|
|
3. **Replace** duplicated code with calls to the helper
|
|
|
|
**Constraints:**
|
|
- Do NOT over-abstract — only extract what's actually duplicated
|
|
- Preserve exact behavior (no logic changes)
|
|
- Ensure all tests still pass
|
|
- Keep changes minimal
|
|
|
|
**Expected output:** DRY code with shared logic extracted into clear, well-named helper functions."
|
|
;;
|
|
|
|
*)
|
|
PROMPT="## Task: Fix $linter warnings in $file (internal/$pkg)
|
|
|
|
**Warnings (this file only):**
|
|
\`\`\`
|
|
$FILE_WARNINGS
|
|
\`\`\`
|
|
|
|
**Instructions:**
|
|
Keep changes minimal and focused. Do not change function signatures or add unnecessary imports. Fix only what the linter flagged."
|
|
;;
|
|
esac
|
|
|
|
echo " Calling AI model..."
|
|
|
|
# Run OpenCode with MiniMax M2.5
|
|
if run_with_timeout "$AIDER_TIMEOUT_SECS" opencode run \
|
|
--model "$MODEL" \
|
|
"$PROMPT" >> "$LOG_FILE" 2>&1; then
|
|
echo " ✓ AI completed successfully"
|
|
else
|
|
AI_EXIT_CODE=$?
|
|
if [ "$AI_EXIT_CODE" -eq 124 ]; then
|
|
echo " ✗ AI call timed out after ${AIDER_TIMEOUT_SECS}s"
|
|
else
|
|
echo " ✗ AI call failed (exit $AI_EXIT_CODE)"
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
# Check if changes were ACTUALLY made
|
|
# OpenCode should only modify code files, not metadata
|
|
if ! git diff --quiet HEAD -- './internal'; then
|
|
echo " Testing changes..."
|
|
|
|
# Run package tests
|
|
if go test "./internal/$pkg/..." >> "$LOG_FILE" 2>&1; then
|
|
echo " ✓ Tests passed"
|
|
|
|
FILE_BASENAME="$(basename "$file")"
|
|
COMMIT_MSG="fix($linter): $FILE_BASENAME in internal/$pkg
|
|
|
|
Fixed $FILE_WARNING_COUNT $linter warnings in $file.
|
|
|
|
Model: $MODEL
|
|
Context: Pulse infrastructure monitoring platform"
|
|
|
|
# Stage only internal/ files (exclude PROGRESS.md, session files, etc.)
|
|
git add internal/
|
|
|
|
if ! git diff --cached --quiet; then
|
|
git commit -m "$COMMIT_MSG" >> "$LOG_FILE" 2>&1
|
|
|
|
TOTAL_COMMITS=$((TOTAL_COMMITS + 1))
|
|
TOTAL_WARNINGS_FIXED=$((TOTAL_WARNINGS_FIXED + FILE_WARNING_COUNT))
|
|
|
|
echo " ✓ Committed ($TOTAL_COMMITS total)"
|
|
else
|
|
echo " ⚠ No changes to commit (AI may have only added comments)"
|
|
git reset HEAD internal/ >/dev/null 2>&1
|
|
fi
|
|
else
|
|
echo " ✗ Tests failed — reverting changes"
|
|
git checkout HEAD -- internal/ >> "$LOG_FILE" 2>&1
|
|
git clean -fd >> "$LOG_FILE" 2>&1
|
|
fi
|
|
else
|
|
echo " ⚠ No code changes made by AI"
|
|
fi
|
|
|
|
echo ""
|
|
done <<< "$FILES"
|
|
|
|
if should_stop; then break; fi
|
|
done
|
|
|
|
PACKAGES_COMPLETED=$((PACKAGES_COMPLETED + 1))
|
|
PKG_ELAPSED=$(( $(date +%s) - PKG_START ))
|
|
|
|
# Update progress file
|
|
cat >> "$PROGRESS_FILE" << PKG_EOF
|
|
|
|
### internal/$pkg
|
|
- Warnings: ${pkg_warning_count}
|
|
- Time: ${PKG_ELAPSED}s
|
|
- Commits: $(git log --oneline "$BEFORE_SHA..HEAD" 2>/dev/null | wc -l | tr -d ' ')
|
|
PKG_EOF
|
|
|
|
# Update status
|
|
sed -i.bak "s/Packages completed: [0-9]*/Packages completed: $PACKAGES_COMPLETED/" "$PROGRESS_FILE" && rm -f "$PROGRESS_FILE.bak"
|
|
sed -i.bak "s/Commits: [0-9]*/Commits: $TOTAL_COMMITS/" "$PROGRESS_FILE" && rm -f "$PROGRESS_FILE.bak"
|
|
sed -i.bak "s/Warnings fixed: [0-9]*/Warnings fixed: $TOTAL_WARNINGS_FIXED/" "$PROGRESS_FILE" && rm -f "$PROGRESS_FILE.bak"
|
|
|
|
echo ""
|
|
done
|
|
|
|
# ── Summary ────────────────────────────────────────────────────────────
|
|
|
|
ELAPSED=$(( $(date +%s) - STARTED_AT ))
|
|
HOURS=$(( ELAPSED / 3600 ))
|
|
MINS=$(( (ELAPSED % 3600) / 60 ))
|
|
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════╗"
|
|
echo "║ Lint Fixer Complete ║"
|
|
echo "╠══════════════════════════════════════════════════════╣"
|
|
echo "║ Packages: $PACKAGES_COMPLETED / ${#PACKAGES_TO_FIX[@]}"
|
|
echo "║ Commits: $TOTAL_COMMITS"
|
|
echo "║ Warnings: $TOTAL_WARNINGS_FIXED fixed"
|
|
echo "║ Duration: ${HOURS}h ${MINS}m"
|
|
echo "╚══════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
if [ "$USE_WORKTREE" = true ]; then
|
|
echo "Review changes:"
|
|
echo " cd $WORK_DIR"
|
|
echo " git log --oneline"
|
|
echo ""
|
|
echo "Merge to main branch:"
|
|
echo " git checkout $(git rev-parse --abbrev-ref HEAD@{-1})"
|
|
echo " git merge $BRANCH"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Progress saved to: $PROGRESS_FILE"
|
|
echo "Full log: $LOG_FILE"
|