Last active
May 12, 2025 18:51
-
-
Save CipherLab/31c87b1f41e791d1f3f9 to your computer and use it in GitHub Desktop.
General useful functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Drawing; | |
using System.Drawing.Imaging; | |
using System.IO; | |
using System.Linq; | |
using System.Net; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using System.Threading.Tasks; | |
using System.Web.Script.Serialization; | |
public class General | |
{ | |
#region colorization code | |
protected Color HexStringToColor(string hex) | |
{ | |
hex = hex.Replace("#", ""); | |
if (hex.Length != 6) | |
throw new Exception(hex + | |
" is not a valid 6-place hexadecimal color code."); | |
string r, g, b; | |
r = hex.Substring(0, 2); | |
g = hex.Substring(2, 2); | |
b = hex.Substring(4, 2); | |
return System.Drawing.Color.FromArgb(HexStringToBase10Int(r), | |
HexStringToBase10Int(g), | |
HexStringToBase10Int(b)); | |
} | |
protected int HexStringToBase10Int(string hex) | |
{ | |
int base10value = 0; | |
try { base10value = System.Convert.ToInt32(hex, 16); } | |
catch { base10value = 0; } | |
return base10value; | |
} | |
static char[] hexDigits = { | |
'0', '1', '2', '3', '4', '5', '6', '7', | |
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; | |
public static string ColorToHexString(Color color) | |
{ | |
byte[] bytes = new byte[3]; | |
bytes[0] = color.R; | |
bytes[1] = color.G; | |
bytes[2] = color.B; | |
char[] chars = new char[bytes.Length * 2]; | |
for (int i = 0; i < bytes.Length; i++) | |
{ | |
int b = bytes[i]; | |
chars[i * 2] = hexDigits[b >> 4]; | |
chars[i * 2 + 1] = hexDigits[b & 0xF]; | |
} | |
return new string(chars); | |
} | |
#endregion | |
private void DeleteFile(string tempFile) | |
{ | |
try | |
{ | |
if (File.Exists(tempFile)) | |
{ | |
File.Delete(tempFile); | |
} | |
} | |
catch { } | |
} | |
public static byte[] StringToByteArray(string str) | |
{ | |
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding(); | |
return encoding.GetBytes(str); | |
} | |
public static string ImageToBase64(Image image, ImageFormat format) | |
{ | |
using (MemoryStream ms = new MemoryStream()) | |
{ | |
// Convert Image to byte[] | |
image.Save(ms, format); | |
byte[] imageBytes = ms.ToArray(); | |
// Convert byte[] to Base64 String | |
string base64String = Convert.ToBase64String(imageBytes); | |
return base64String; | |
} | |
} | |
public static string ByteArrayToString(byte[] input) | |
{ | |
UTF8Encoding enc = new UTF8Encoding(); | |
string str = enc.GetString(input); | |
return str; | |
} | |
public static Image getImageFromURL(string url) | |
{ | |
if (string.IsNullOrWhiteSpace(url)) | |
{ | |
url = "http://i.imgur.com/zxBgpGu.png"; | |
} | |
var webClient = new WebClient(); | |
byte[] imageBytes = webClient.DownloadData(url); | |
return byteArrayToImage(imageBytes); | |
} | |
public static Image byteArrayToImage(byte[] byteArrayIn) | |
{ | |
try | |
{ | |
MemoryStream ms = new MemoryStream(byteArrayIn, 0, byteArrayIn.Length); | |
ms.Write(byteArrayIn, 0, byteArrayIn.Length); | |
return Image.FromStream(ms, true); | |
} | |
catch | |
{ | |
} | |
return null; | |
} | |
public static string CsvToJson(string value) | |
{ | |
// Get lines. | |
if (value == null) return null; | |
string[] lines = value.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); | |
if (lines.Length < 2) throw new InvalidDataException("Must have header line."); | |
// Get headers. | |
string[] headers = lines.First().Split(','); | |
// Build JSON array. | |
StringBuilder sb = new StringBuilder(); | |
sb.AppendLine("["); | |
for (int i = 1; i < lines.Length; i++) | |
{ | |
//string[] fields = lines[i].Split(','); | |
List<string> fields = new List<string>(); | |
var regex = new Regex("(?<=^|,)(\"(?:[^\"]|\"\")*\"|[^,]*)"); | |
foreach (Match m in regex.Matches(lines[i])) | |
{ | |
fields.Add(m.Value.Replace("\"", "")); | |
} | |
if (fields.Count != headers.Length) throw new InvalidDataException("Field count must match header count."); | |
var jsonElements = headers.Zip(fields, (header, field) => string.Format("\"{0}\": \"{1}\"", header, field)).ToArray(); | |
string jsonObject = "{" + string.Format("{0}", string.Join(",", jsonElements)) + "}"; | |
if (i < lines.Length - 1) | |
jsonObject += ","; | |
sb.AppendLine(jsonObject); | |
} | |
sb.AppendLine("]"); | |
return sb.ToString(); | |
} | |
public static string ObjectToJson(object obj) | |
{ | |
var js = new JavaScriptSerializer(); | |
return js.Serialize(obj); | |
} | |
public static object JsonToObject(string datain, Type objectType) | |
{ | |
var js = new JavaScriptSerializer(); | |
return js.Deserialize(datain, objectType); | |
} | |
public static bool IsNumber(string s) | |
{ | |
return s.All(char.IsDigit); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Default repository path (adjust if needed) | |
DEFAULT_REPO_PATH="/home/mnewport/repos/wyffels/Sales-App" | |
# On WSL, you might need the Linux path directly like above, | |
# or translate the Windows path if running Bash outside WSL directly: | |
# DEFAULT_REPO_PATH="/mnt/c/path/to/your/repo" | |
# --- Argument Parsing --- | |
FORCE_FETCH=false | |
SHOW_HELP=false | |
day_offset_param="" | |
author_index_param="" | |
# Process command line options | |
while getopts "fhr:d:a:" opt; do | |
case ${opt} in | |
f ) # Force fetch | |
FORCE_FETCH=true | |
;; | |
r ) # Custom repo path | |
custom_repo_path=$OPTARG | |
;; | |
h ) # Help | |
SHOW_HELP=true | |
;; | |
d ) # Day offset | |
day_offset_param=$OPTARG | |
;; | |
a ) # Author index | |
author_index_param=$OPTARG | |
;; | |
\? ) | |
echo "Usage: $0 [-f] [-r repo_path] [-d day_offset] [-a author_index] [-h]" | |
exit 1 | |
;; | |
esac | |
done | |
shift $((OPTIND -1)) | |
# Remains of the debug function - now just a void where error messages go to die | |
# (No functions needed here now) | |
# --- Global Helper Function: Animation --- | |
show_animation() { | |
local pid=$1 | |
local initial_message=$2 | |
local frames=('⣾' '⣽' '⣻' '⢿' '⡿' '⣟' '⣯' '⣷') | |
# Generic messages suitable for various waiting periods | |
local messages=( | |
"Still waiting..." | |
"Processing data..." | |
"Fetching information..." | |
"Thinking really hard..." | |
"Almost there..." | |
"Checking the archives..." | |
"Consulting the git gods..." | |
"Wrangling bits and bytes..." | |
) | |
local count=0 | |
local message="$initial_message" # Start with the initial message | |
echo # Newline before animation starts | |
while ps -p $pid > /dev/null; do | |
frame=${frames[count % ${#frames[@]}]} | |
# Change message every ~5 seconds (15 * 0.3s) | |
if [ $((count % 15)) -eq 0 ] && [ $count -ne 0 ]; then | |
message=${messages[$((RANDOM % ${#messages[@]}))]} | |
fi | |
printf "\r\033[K %s %s" "$frame" "$message" | |
sleep 0.3 | |
count=$((count+1)) | |
done | |
printf "\r\033[K" # Clear the animation line | |
echo # Newline after animation finishes | |
} | |
# First positional parameter as repo path if provided and -r not used | |
if [ -z "$custom_repo_path" ] && [ $# -gt 0 ]; then | |
custom_repo_path="$1" | |
fi | |
# Show help if requested | |
if [ "$SHOW_HELP" = true ]; then | |
cat << "EOF" | |
╔════════════════════════════════════════════════════════════╗ | |
║ GIT ARCHAEOLOGY & ACCOUNTABILITY DISTRIBUTION ║ | |
╚════════════════════════════════════════════════════════════╝ | |
USAGE: ./git_commits.sh [-f] [-r repo_path] [-d day_offset] [-a author_index] [-h] | |
OPTIONS: | |
-f Force branch fetching (ignores cache) | |
For when you need to refresh your list of | |
things to never look at | |
-r repo_path Specify a repository path | |
Because pointing fingers at other directories | |
should be easier | |
-d day_offset Specify date offset (0 today, -1 yesterday, etc.) | |
Skip the date prompt | |
-a index Specify author index (1 for first author, etc.) | |
Skip the author selection prompt | |
-h Show this help | |
The closest you'll get to actual documentation | |
This script answers the age-old question: "What did I actually | |
do today that I can talk about in standup tomorrow?" by providing | |
a clean, timesheet-ready output. | |
It also calculates the total time you spent committing, which | |
bears no relation to the time you spent actually working. | |
Note: No git branches were harmed in the execution of this script, | |
though several were thoroughly judged. | |
EOF | |
exit 0 | |
fi | |
# Repository path handling | |
repo_path="${custom_repo_path:-$DEFAULT_REPO_PATH}" | |
# --- Basic Validation --- | |
if [ ! -d "$repo_path" ]; then | |
echo "Error: Repository directory not found: $repo_path" >&2 | |
exit 1 | |
fi | |
# Change to the repository directory | |
cd "$repo_path" || { echo "Error: Could not change directory to $repo_path" >&2; exit 1; } | |
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then | |
echo "Error: Not a valid Git repository: $repo_path" >&2 | |
exit 1 | |
fi | |
# --- Cache Remote Branches Analysis --- | |
cache_remote_branches() { | |
local cache_path="/tmp/git_remote_branches_cache_$(basename $(git rev-parse --show-toplevel))" | |
local cache_timeout=86400 # 24 hours in seconds | |
# Check if cache exists and is recent | |
if [ -f "$cache_path" ]; then | |
local cache_time=$(stat -c %Y "$cache_path" 2>/dev/null || stat -f %m "$cache_path" 2>/dev/null) # Added error suppression for stat -f | |
local current_time=$(date +%s) | |
local cache_age=$((current_time - cache_time)) | |
if [ "$cache_age" -lt "$cache_timeout" ] && [ "$FORCE_FETCH" = false ]; then | |
echo "🧠 Using cached branch data (Cache is $((cache_age / 60)) minutes old). Use -f to force fetch." | |
return 0 | |
elif [ "$FORCE_FETCH" = true ]; then | |
echo "🔥 Force fetch enabled! Ignoring cache." | |
rm -f "$cache_path" | |
else | |
echo "⏰ Cache expired. Fetching fresh data." | |
rm -f "$cache_path" | |
fi | |
fi | |
echo "🔄 Fetching ALL branches from ALL remotes..." | |
{ # Run fetch in background to show animation for it too | |
git fetch --all --prune | |
} & | |
local fetch_pid=$! | |
show_animation $fetch_pid "Fetching remote objects..." | |
wait $fetch_pid | |
echo "🌱 Creating local tracking branches (if needed)..." | |
# Run the branch creation process in the background and create cache | |
{ | |
# Write remote branches to cache first | |
# Ensure color codes are stripped even if git config changes | |
git branch -r --no-color | grep -v '\->' > "$cache_path" | |
# Create the local tracking branches based on the cache file | |
while IFS= read -r remote; do | |
# Trim whitespace | |
remote=$(echo "$remote" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') | |
if [ -z "$remote" ]; then continue; fi | |
# Extract the base branch name (strip remote name like 'origin/') | |
# Assuming remote format is 'origin/branch-name' | |
if [[ "$remote" == *"/"* ]]; then | |
local_branch="${remote#*/}" | |
else | |
# Handle cases where remote might not have a slash (unlikely for standard setups) | |
local_branch="$remote" | |
fi | |
if [ -z "$local_branch" ]; then continue; fi # Skip empty lines if any | |
# Check if local branch *already exists* before trying to create it | |
if ! git show-ref --verify --quiet refs/heads/"$local_branch"; then | |
# Attempt to track; suppress stderr for "already exists" which can happen in races | |
git branch --track "$local_branch" "$remote" >/dev/null 2>&1 | |
fi | |
done < "$cache_path" | |
# Update cache timestamp only after successfully processing | |
touch "$cache_path" | |
} & # End background process block | |
local branch_pid=$! | |
show_animation $branch_pid "Syncing local branches..." # Pass PID and initial message | |
wait $branch_pid # Wait for the background branch creation to finish | |
echo "✅ Branch collection complete." | |
echo "🧠 Branches cached for future script runs." | |
echo | |
} | |
# --- Fetch ALL Remote Branches --- | |
cache_remote_branches | |
# --- Date Input --- | |
if [ -n "$day_offset_param" ]; then | |
day_offset="$day_offset_param" | |
else | |
echo "Enter the date (0 today, -1 yesterday, etc.) to filter commits:" | |
read -r day_offset | |
# Default to 0 if empty | |
day_offset="${day_offset:-0}" | |
fi | |
# Validate numeric input | |
if ! [[ "$day_offset" =~ ^-?[0-9]+$ ]]; then | |
echo "Error: Invalid offset. Please enter an integer." >&2; exit 1 | |
fi | |
# Calculate target date (YYYY-MM-DD) - Assumes GNU date | |
target_date=$(date -d "$day_offset days" '+%Y-%m-%d') | |
if [ $? -ne 0 ]; then | |
# Fallback for non-GNU date (macOS) - less reliable for negative offsets | |
target_date=$(date -v"${day_offset}d" '+%Y-%m-%d' 2>/dev/null) | |
if [ $? -ne 0 ]; then | |
echo "Error: Could not calculate date. Ensure 'date' command supports relative dates." >&2; exit 1 | |
fi | |
fi | |
echo "Filtering for date: $target_date" | |
# Define start and end timestamps for the target date | |
start_date="${target_date}T00:00:00" | |
end_date="${target_date}T23:59:59" | |
# --- Author Filtering --- | |
echo -e "\nFetching authors..." | |
mapfile -t author_lines < <(git log --all --no-merges --format='%an' | sort | uniq -c | sort -nr) # Use --all instead of --branches | |
if [ ${#author_lines[@]} -eq 0 ]; then | |
echo "No authors found in the repository." | |
exit 0 | |
fi | |
echo -e "\nUnique authors in the repository:" | |
authors=() | |
declare -A author_map # Map index to name | |
for i in "${!author_lines[@]}"; do | |
line="${author_lines[$i]}" | |
# Robust parsing for names potentially containing numbers | |
count=$(echo "$line" | awk '{print $1}') | |
name=$(echo "$line" | sed -e 's/^[[:space:]]*[0-9]*[[:space:]]*//') # Remove leading count and space | |
authors+=("$name") | |
author_map[$((i+1))]="$name" | |
printf "%d. %s - Commits: %d\n" "$((i+1))" "$name" "$count" | |
done | |
if [ -n "$author_index_param" ]; then | |
author_index="$author_index_param" | |
else | |
echo -e "\nEnter the number corresponding to the author (default 1):" | |
read -r author_index | |
author_index="${author_index:-1}" | |
fi | |
if ! [[ "$author_index" =~ ^[0-9]+$ ]] || [ "$author_index" -lt 1 ] || [ "$author_index" -gt ${#authors[@]} ]; then | |
echo "Error: Invalid author number." >&2; exit 1 | |
fi | |
selected_author="${author_map[$author_index]}" | |
echo "Selected author: $selected_author" | |
# --- Commit Retrieval & Processing --- | |
echo -e "\nFetching commits for $selected_author on $target_date..." | |
# Use a character that should NEVER appear in git commit messages | |
# Record Separator (ASCII 30) is ideal for this purpose | |
SEP=$'\036' | |
commit_details=() # Initialize empty array | |
commit_output_file="/tmp/git_commits_output_$$" # Use $$ for better uniqueness | |
# Use EXIT trap for robust cleanup | |
trap 'rm -f "$commit_output_file"' EXIT | |
# Run git log in the background | |
{ | |
# Use --all to search all refs (local branches, remote branches, tags) | |
# Protect against SEP in messages (though highly unlikely) | |
git log --all --no-merges --author="$selected_author" --after="$start_date" --before="$end_date" --date=iso --format="%at${SEP}%H${SEP}%ai${SEP}%s${SEP}%b" --reverse > "$commit_output_file" | |
git_status=$? # Capture exit status inside the subshell | |
exit $git_status # Exit subshell with git log's status | |
} & | |
commit_pid=$! | |
# Show animation while git log runs | |
show_animation $commit_pid "Digging through commit history..." | |
# Wait for git log to finish and capture its exit status | |
wait $commit_pid | |
git_log_exit_status=$? | |
# Check if the command failed | |
if [ $git_log_exit_status -ne 0 ]; then | |
echo "Error: git log command failed while fetching commits (Exit code: $git_log_exit_status)." >&2 | |
# Optionally print stderr from git log if captured, but it wasn't here. | |
exit 1 | |
fi | |
# Read the results from the temporary file if it has content | |
if [ -s "$commit_output_file" ]; then | |
# Use safe mapfile/read loop approach | |
while IFS= read -r line || [[ -n "$line" ]]; do # Process last line even if no trailing newline | |
if [ -n "$line" ]; then # Skip empty lines | |
commit_details+=("$line") | |
fi | |
done < "$commit_output_file" | |
else | |
# Handle case where git log ran successfully but produced no output | |
commit_details=() | |
fi | |
# --- Process Commits --- | |
if [ ${#commit_details[@]} -eq 0 ]; then | |
echo "No commits found for '$selected_author' on $target_date." | |
# Print the final clean footer even if no commits | |
echo -e "\n╔═════════════════════════════════════════════════════════╗" | |
echo -e "║ COMMITS FOR TIMESHEET: $target_date ║" | |
echo -e "╚═════════════════════════════════════════════════════════╝" | |
echo "* Branches: N/A" | |
printf "\n* Time logged: 00:00\n" | |
echo -e "╔═════════════════════════════════════════════════════════╗" | |
echo -e "║ END TIMESHEET DATA ║" | |
echo -e "╚═════════════════════════════════════════════════════════╝" | |
exit 0 | |
fi | |
# Always print the clean header | |
echo -e "\n╔═════════════════════════════════════════════════════════╗" | |
echo -e "║ COMMITS FOR TIMESHEET: $target_date ║" | |
echo -e "╚═════════════════════════════════════════════════════════╝" | |
declare -A branch_commits # Associative array: branch -> list of commit details strings (newline separated) | |
declare -A branch_first_commit_time # Associative array: branch -> earliest timestamp | |
declare -A branch_last_segment_set # Use keys for uniqueness of last segments | |
first_commit_timestamp="" | |
last_commit_timestamp="" | |
echo "Associating commits with branches..." # Add progress indicator | |
# Process each commit detail line to find its branches | |
processed_count=0 | |
total_commits=${#commit_details[@]} | |
for details in "${commit_details[@]}"; do | |
# Use a safer field splitting approach with read | |
IFS="$SEP" read -r timestamp sha datetime subject body_rest <<< "$details" | |
# Validate SHA exists - CRITICAL CHECK | |
if [ -z "$sha" ]; then | |
#echo "Skip: Missing SHA in line: $details" >&2 # More informative warning | |
#processed_count=$((processed_count + 1)) | |
printf "\rProcessed %d / %d commits..." "$processed_count" "$total_commits" | |
continue # Skip to next commit | |
fi | |
# Validate timestamp is a number to avoid comparison errors | |
if [[ "$timestamp" =~ ^[0-9]+$ ]]; then | |
# Update overall first/last timestamps | |
if [ -z "$first_commit_timestamp" ] || [ "$timestamp" -lt "$first_commit_timestamp" ]; then | |
first_commit_timestamp="$timestamp" | |
fi | |
if [ -z "$last_commit_timestamp" ] || [ "$timestamp" -gt "$last_commit_timestamp" ]; then | |
last_commit_timestamp="$timestamp" | |
fi | |
else | |
echo "WARNING: Invalid timestamp for commit ${sha:0:8}: '$timestamp'" >&2 | |
# Try to recover timestamp from datetime if possible | |
new_timestamp=$(date -d "$(echo "$datetime" | cut -d' ' -f1,2)" +%s 2>/dev/null) | |
if [[ "$new_timestamp" =~ ^[0-9]+$ ]]; then | |
timestamp="$new_timestamp" | |
# Re-run timestamp comparison logic with recovered value | |
if [ -z "$first_commit_timestamp" ] || [ "$timestamp" -lt "$first_commit_timestamp" ]; then first_commit_timestamp="$timestamp"; fi | |
if [ -z "$last_commit_timestamp" ] || [ "$timestamp" -gt "$last_commit_timestamp" ]; then last_commit_timestamp="$timestamp"; fi | |
else | |
# Fallback if recovery fails | |
if [ -n "$first_commit_timestamp" ]; then timestamp="$first_commit_timestamp"; else timestamp=$(date +%s); fi | |
fi | |
fi | |
# Safely get branches containing this commit (local and remote) | |
# Use --all to get local and remote branches directly | |
# Use --no-color to prevent issues | |
# Remove 'remotes/origin/' prefix commonly added by --all | |
mapfile -t branches < <(git branch --all --no-color --contains "$sha" --format='%(refname:short)' 2>/dev/null | sed 's|^remotes/origin/|origin/|' | sed 's|^origin/||') # Strip origin/ prefix | |
# Last resort fallback | |
if [ ${#branches[@]} -eq 0 ]; then | |
branches=("(no associated branch found)") | |
fi | |
# Store commit in each branch's commit list | |
for branch_name in "${branches[@]}"; do | |
# Trim potential whitespace from branch names | |
branch_name=$(echo "$branch_name" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') | |
effective_branch_name="${branch_name:-"(unknown branch)"}" | |
# IMPORTANT: Use a newline separator for commit records within a branch | |
# Ensure the 'details' variable itself doesn't end with newline if IFS trimming removed it | |
# Safest to reconstruct the line if needed, or just append newline. | |
branch_commits["$effective_branch_name"]+="${details}"$'\n' | |
# Track earliest commit per branch for sorting | |
if [ -z "${branch_first_commit_time[$effective_branch_name]}" ] || | |
[ "$timestamp" -lt "${branch_first_commit_time[$effective_branch_name]}" ]; then | |
branch_first_commit_time["$effective_branch_name"]="$timestamp" | |
fi | |
# Track branch name last segments for summary | |
last_segment=$(basename "$effective_branch_name" 2>/dev/null || echo "$effective_branch_name") | |
# Don't add empty or special names to segments | |
if [[ -n "$last_segment" && "$last_segment" != "(no associated branch found)" && "$last_segment" != "(unknown branch)" ]]; then | |
branch_last_segment_set["$last_segment"]=1 | |
fi | |
done | |
processed_count=$((processed_count + 1)) | |
# Simple progress indicator without animation | |
printf "\rProcessed %d / %d commits..." "$processed_count" "$total_commits" | |
done | |
printf "\r\033[K" # Clear progress line | |
echo "Sorting and formatting results..." | |
# --- Sort Branches by First Commit Time --- | |
if [ ${#branch_first_commit_time[@]} -gt 0 ]; then | |
mapfile -t sorted_branch_names < <( | |
for branch in "${!branch_first_commit_time[@]}"; do | |
printf "%s\t%s\n" "${branch_first_commit_time[$branch]}" "$branch" | |
done | sort -n | cut -f2- | |
) | |
else | |
# No branches found - rare but possible | |
sorted_branch_names=() | |
fi | |
# --- Output Commits Grouped by Branch (with De-duplication) --- | |
declare -A seen_commits # Track SHAs that have already been printed | |
for branch_name in "${sorted_branch_names[@]}"; do | |
# Get commit details for the branch, ensuring trailing newline for readarray | |
branch_content="${branch_commits[$branch_name]}" | |
if [ -z "$branch_content" ]; then | |
continue # Skip empty branch content | |
fi | |
# Create temporary array of sorted commits for this branch using process substitution | |
# Sort lines numerically based on the first field (timestamp) | |
mapfile -t commits_for_branch < <(echo -n "$branch_content" | sort -t"$SEP" -k1,1n) | |
branch_header_printed=0 | |
for commit_line in "${commits_for_branch[@]}"; do | |
if [ -z "$commit_line" ]; then continue; fi | |
# Split fields safely | |
IFS="$SEP" read -r timestamp sha datetime subject body <<< "$commit_line" | |
# Validate SHA before proceeding | |
if [ -z "$sha" ]; then | |
echo "WARNING: Skipping display for commit with missing SHA in final output." >&2 | |
continue | |
fi | |
# De-duplication check | |
if [[ -n "${seen_commits["$sha"]}" ]]; then | |
continue # Skip already printed commit | |
fi | |
# Parse time for display (handle ISO 8601 format from %ai) | |
# Example: 2025-04-02 09:40:15 -0500 | |
time_str=$(echo "$datetime" | awk '{print $2}' | cut -d: -f1,2 2>/dev/null || echo "??:??") | |
# Skip invalid entries | |
if [ -z "$time_str" ] || [ "$time_str" = "??:??" ]; then | |
echo "Skipping commit with invalid time: ${sha:0:8} ($datetime)" >&2 | |
continue | |
fi | |
# Ensure subject is not just whitespace | |
trimmed_subject=$(echo "$subject" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') | |
if [ -z "$trimmed_subject" ]; then | |
echo "Skipping commit with empty subject: ${sha:0:8}" >&2 | |
continue | |
fi | |
# Print branch header if first valid commit in branch | |
if [ "$branch_header_printed" -eq 0 ]; then | |
echo -e "\n╭─────────────────────────────────────────────────────╮" | |
printf "│ %-51s │\n" "BRANCH: $branch_name" | |
echo -e "╰─────────────────────────────────────────────────────╯" | |
branch_header_printed=1 | |
fi | |
# Mark commit as seen | |
seen_commits["$sha"]=1 | |
# Print the commit | |
printf -- " - [%s] %s\n" "$time_str" "$subject" # Use original subject | |
# Print first line of body if it looks like a proper comment | |
if [ -n "$body" ]; then | |
# Read the first line without external commands | |
IFS= read -r first_line <<< "$body" | |
trimmed_first_line=$(echo "$first_line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') | |
# Check if it's non-empty and doesn't look like commit metadata or separator line | |
if [[ -n "$trimmed_first_line" ]] && ! [[ "$trimmed_first_line" =~ ^(Signed-off-by:|Co-authored-by:|Change-Id:|Fixes:|See-also:|---|Merge:|\*\ ) ]]; then | |
printf " %s\n" "$first_line" | |
fi | |
fi | |
done | |
done | |
# --- Print Distinct Branch Last Segments (Clean Format) --- | |
if [ ${#branch_last_segment_set[@]} -gt 0 ]; then | |
mapfile -t sorted_last_segments < <(printf "%s\n" "${!branch_last_segment_set[@]}" | sort) | |
distinct_segments_str=$(printf "%s, " "${sorted_last_segments[@]}") | |
distinct_segments_str=${distinct_segments_str%, } # Remove trailing comma and space | |
else | |
distinct_segments_str="N/A" | |
fi | |
echo -e "\n* Branches: ${distinct_segments_str}" # Added newline for spacing | |
# --- Calculate and Print Total Time Span (Clean Format) --- | |
if [ -n "$first_commit_timestamp" ] && [ -n "$last_commit_timestamp" ] && [[ "$last_commit_timestamp" =~ ^[0-9]+$ ]] && [[ "$first_commit_timestamp" =~ ^[0-9]+$ ]] && [ "$last_commit_timestamp" -ge "$first_commit_timestamp" ]; then | |
total_seconds=$((last_commit_timestamp - first_commit_timestamp)) | |
total_hours=$((total_seconds / 3600)) | |
total_minutes=$(((total_seconds % 3600) / 60)) | |
printf "\n* Time logged: %02d:%02d\n" "$total_hours" "$total_minutes" | |
else | |
# Handle cases where timestamps were invalid or missing | |
echo "Could not calculate time span due to missing or invalid timestamps." >&2 | |
printf "\n* Time logged: 00:00\n" | |
fi | |
echo -e "╔═════════════════════════════════════════════════════════╗" | |
echo -e "║ END TIMESHEET DATA ║" | |
echo -e "╚═════════════════════════════════════════════════════════╝" | |
exit 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# terminal_ascension_v3.sh - The Sacred Terminal Transformation Ritual | |
# For Arch Linux & Manjaro users who've decided their terminal should work for them, | |
# not against them, unlike their corporate overlords | |
set -e # Exit immediately if a command exits with a non-zero status | |
# Because nothing says "I've given up" like letting errors silently accumulate | |
echo "================================================================" | |
echo " TERMINAL ASCENSION RITUAL v3 - PROCEED WITH REVERENCE " | |
echo " Your journey from terminal peasant to CLI royalty begins " | |
echo "================================================================" | |
# Create a log file to record our spiritual journey | |
LOG_FILE="$HOME/terminal_ascension_log.txt" | |
exec > >(tee -a "$LOG_FILE") 2>&1 | |
echo "📝 Creating backup directory for your existing configs" | |
BACKUP_DIR="$HOME/.terminal_backup_$(date +%Y%m%d%H%M%S)" | |
mkdir -p "$BACKUP_DIR" | |
# Function to backup existing config files before we obliterate them with progress | |
backup_file() { | |
if [ -f "$1" ]; then | |
echo "📦 Backing up $1 to $BACKUP_DIR" | |
cp "$1" "$BACKUP_DIR/" | |
fi | |
} | |
# Backup existing configuration files | |
backup_file "$HOME/.zshrc" | |
backup_file "$HOME/.zpreztorc" | |
backup_file "$HOME/.config/kitty/kitty.conf" | |
backup_file "$HOME/.config/starship.toml" | |
echo "🔍 Checking for required packages..." | |
# Function to check if a command exists | |
command_exists() { | |
command -v "$1" >/dev/null 2>&1 | |
} | |
# Function to install a package if it doesn't exist | |
ensure_package() { | |
if ! command_exists "$1"; then | |
echo "📦 Installing $1..." | |
sudo pacman -S --noconfirm "$1" || { echo "❌ Failed to install $1"; exit 1; } | |
else | |
echo "✅ $1 is already installed" | |
fi | |
} | |
# Install core packages | |
echo "🚀 Installing the digital cornerstones of your new terminal empire..." | |
PACKAGES=( | |
"kitty" # Because terminal emulators should be named after small felines | |
"zsh" # The shell that makes bash look like it's still using dial-up | |
"fzf" # For when you remember typing something, somewhere, sometime | |
"git" # The historian of your coding mistakes | |
"bat" # cat with wings, literally | |
"exa" # ls on steroids, because listing files should be an experience | |
"ripgrep" # grep but fast enough to match your diminishing patience | |
"fd" # find but without the arcane syntax from the 1970s | |
"gitui" # For when you're tired of memorizing git's 167 subcommands | |
) | |
for package in "${PACKAGES[@]}"; do | |
ensure_package "$package" | |
done | |
# Install fonts - because reading monospace without ligatures is like eating unseasoned food | |
echo "🔤 Installing fonts that won't make your eyes bleed..." | |
if ! pacman -Qi ttf-firacode-nerd > /dev/null 2>&1; then | |
sudo pacman -S --noconfirm ttf-firacode-nerd || echo "⚠️ Couldn't install Nerd Fonts. Your symbols may look like hieroglyphics." | |
else | |
echo "✅ Nerd Fonts already installed" | |
fi | |
# Install Starship - because prompts should be informative, not cryptic | |
echo "🚀 Installing Starship prompt - the GPS for your terminal journey..." | |
if ! command_exists starship; then | |
curl -sS https://starship.rs/install.sh | sh || echo "⚠️ Failed to install Starship. Your prompt will remain ordinary." | |
else | |
echo "✅ Starship is already installed" | |
fi | |
# Install Atuin - because your command history deserves better than a flat file | |
echo "🪄 Installing Atuin - the magical history manager..." | |
if ! command_exists atuin; then | |
sudo pacman -S --noconfirm atuin || echo "⚠️ Failed to install Atuin. Your history will remain in the dark ages." | |
else | |
echo "✅ Atuin is already installed" | |
fi | |
# Install Prezto - the Zsh framework that won't eat your CPU for breakfast | |
echo "🏗️ Setting up Prezto - your new Zsh framework..." | |
if [ ! -d "${ZDOTDIR:-$HOME}/.zprezto" ]; then | |
git clone --recursive https://github.com/sorin-ionescu/prezto.git "${ZDOTDIR:-$HOME}/.zprezto" || { echo "❌ Failed to clone Prezto"; exit 1; } | |
echo "📝 Creating Zsh config files..." | |
# Using bash-compatible commands instead of Zsh-specific globbing | |
for rcfile in $(find "${ZDOTDIR:-$HOME}"/.zprezto/runcoms -type f -not -name "README.md"); do | |
ln -sf "$rcfile" "${ZDOTDIR:-$HOME}/.$(basename $rcfile)" | |
done | |
else | |
echo "✅ Prezto is already installed" | |
fi | |
# Install per-directory-history - because context is everything | |
echo "🗂️ Installing per-directory-history plugin..." | |
if [ ! -d "$HOME/per-directory-history" ]; then | |
git clone https://github.com/jimhester/per-directory-history "$HOME/per-directory-history" || echo "⚠️ Failed to install per-directory-history. Your history will remain contextless." | |
else | |
echo "✅ per-directory-history is already installed" | |
fi | |
# Configure Kitty | |
echo "🐱 Configuring Kitty - your new terminal emulator..." | |
mkdir -p "$HOME/.config/kitty" | |
cat > "$HOME/.config/kitty/kitty.conf" << 'EOF' | |
# Kitty Configuration - Because terminals should be pretty AND functional | |
# Font settings - for when you stare at text all day | |
font_family FiraCode Nerd Font | |
bold_font auto | |
italic_font auto | |
bold_italic_font auto | |
font_size 12.0 | |
# Visual tweaks - because aesthetics matter to the deeply jaded | |
background_opacity 0.95 | |
window_padding_width 5 | |
placement_strategy center | |
hide_window_decorations no | |
confirm_os_window_close 0 | |
# Because bell sounds make people violent | |
enable_audio_bell no | |
visual_bell_duration 0.0 | |
# Terminal performance settings - for when you cat a 10GB log file by accident | |
scrollback_lines 10000 | |
input_delay 3 | |
repaint_delay 10 | |
sync_to_monitor yes | |
# URL handling - because clicking is easier than copying | |
url_style curly | |
open_url_with default | |
url_prefixes http https file ftp gemini irc gopher mailto news git | |
detect_urls yes | |
# Shortcuts that won't require finger gymnastics | |
map ctrl+shift+c copy_to_clipboard | |
map ctrl+shift+v paste_from_clipboard | |
map ctrl+shift+s paste_from_selection | |
map ctrl+shift+t new_tab | |
map ctrl+shift+w close_tab | |
map ctrl+shift+l next_tab | |
map ctrl+shift+h previous_tab | |
map ctrl+shift+n new_window | |
map ctrl+shift+q close_window | |
map ctrl+shift+equal increase_font_size | |
map ctrl+shift+minus decrease_font_size | |
map ctrl+shift+backspace restore_font_size | |
# Tab bar - because spatial awareness matters | |
tab_bar_edge top | |
tab_bar_style powerline | |
tab_powerline_style slanted | |
active_tab_foreground #000 | |
active_tab_background #eee | |
active_tab_font_style bold | |
inactive_tab_foreground #444 | |
inactive_tab_background #999 | |
inactive_tab_font_style normal | |
# Colors - because black on white is for printed books | |
foreground #dddddd | |
background #121212 | |
cursor #cccccc | |
EOF | |
# Configure Starship | |
echo "🌠 Configuring Starship - your new prompt..." | |
mkdir -p "$HOME/.config" | |
cat > "$HOME/.config/starship.toml" << 'EOF' | |
# Starship Configuration - For prompts that convey actual useful information | |
# General settings | |
add_newline = true | |
scan_timeout = 30 | |
command_timeout = 500 | |
# The iconic prompt character | |
[character] | |
success_symbol = "[λ](green)" | |
error_symbol = "[✗](red)" | |
vimcmd_symbol = "[](green)" | |
# Directory - where am I and how deep in the filesystem? | |
[directory] | |
truncation_length = 3 | |
truncate_to_repo = true | |
style = "blue" | |
read_only = " 🔒" | |
read_only_style = "red" | |
# Git - what branch and are you behind/ahead? | |
[git_branch] | |
symbol = " " | |
style = "purple" | |
truncation_length = 18 | |
truncation_symbol = "..." | |
[git_status] | |
format = '([\[$all_status$ahead_behind\]]($style) )' | |
style = "red" | |
conflicted = "⚔️ " | |
ahead = "⇡${count}" | |
behind = "⇣${count}" | |
diverged = "⇕⇡${ahead_count}⇣${behind_count}" | |
untracked = "?${count}" | |
stashed = "*${count}" | |
modified = "!${count}" | |
staged = "+${count}" | |
renamed = "»${count}" | |
deleted = "✘${count}" | |
# Language versions - what runtime are you actually using? | |
[nodejs] | |
format = "via [⬢ $version](green) " | |
[dotnet] | |
symbol = " " | |
style = "blue" | |
heuristic = true | |
[python] | |
symbol = "🐍 " | |
pyenv_version_name = true | |
detect_files = ["requirements.txt", ".python-version", "pyproject.toml", "Pipfile", "tox.ini", "setup.py", "__init__.py"] | |
# Execution time - how slow was that command? | |
[cmd_duration] | |
min_time = 2000 | |
format = "took [$duration](yellow) " | |
# Package version - because knowing is half the battle | |
[package] | |
format = "via [📦 $version](208) " | |
# Containers - because forgetting you're in a container leads to confusion | |
[container] | |
format = "[$symbol]($style) " | |
# Status code - when things go wrong and you need to know why | |
[status] | |
disabled = false | |
format = "[$symbol$status]($style) " | |
symbol = "✖ " | |
success_symbol = "" | |
not_executable_symbol = "🚫 " | |
not_found_symbol = "🔍 " | |
sigint_symbol = "🧱 " | |
signal_symbol = "⚡ " | |
style = "bold red" | |
# Hostname - for when you're SSH'd into 17 different servers | |
[hostname] | |
ssh_only = true | |
format = "on [$hostname](bold red) " | |
EOF | |
# Configure Zsh with Prezto, FZF, and other enhancements | |
echo "🛠️ Configuring Zsh - your new command shell..." | |
cat > "$HOME/.zshrc" << 'EOF' | |
# .zshrc - Where productivity dreams come true and muscle memory goes to die | |
# Source Prezto configuration | |
if [[ -s "${ZDOTDIR:-$HOME}/.zprezto/init.zsh" ]]; then | |
source "${ZDOTDIR:-$HOME}/.zprezto/init.zsh" | |
fi | |
# Set Prezto options - loaded modules | |
zstyle ':prezto:load' pmodule \ | |
'environment' \ | |
'terminal' \ | |
'editor' \ | |
'history' \ | |
'directory' \ | |
'spectrum' \ | |
'utility' \ | |
'completion' \ | |
'git' \ | |
'syntax-highlighting' \ | |
'history-substring-search' \ | |
'autosuggestions' | |
# Theme settings - because life's too short for ugly prompts | |
zstyle ':prezto:module:prompt' theme 'sorin' | |
# Loading per-directory-history - context-aware history | |
source ~/per-directory-history/per-directory-history.zsh | |
# FZF integration - for fuzzy finding everything | |
if [ -f /usr/share/fzf/key-bindings.zsh ]; then | |
source /usr/share/fzf/key-bindings.zsh | |
fi | |
if [ -f /usr/share/fzf/completion.zsh ]; then | |
source /usr/share/fzf/completion.zsh | |
fi | |
# Better tool alternatives - because life's too short for outdated tools | |
alias cat='bat --style=plain' | |
alias ls='exa --icons' | |
alias ll='exa -la --icons' | |
alias la='exa -a --icons' | |
alias lt='exa -T --icons' | |
alias grep='rg' | |
alias find='fd' | |
alias ps='procs' | |
alias du='dust' | |
# Atuin history management - because remembering is hard | |
eval "$(atuin init zsh)" | |
# Starship prompt - your CLI GPS | |
eval "$(starship init zsh)" | |
# Directory navigation - because cd is so 1970s | |
alias ..='cd ..' | |
alias ...='cd ../..' | |
alias ....='cd ../../..' | |
# Git aliases - because git commands are unnecessarily verbose | |
alias gs='git status' | |
alias ga='git add' | |
alias gc='git commit' | |
alias gco='git checkout' | |
alias gl='git log --oneline' | |
alias gp='git push' | |
alias gpull='git pull' | |
alias gd='git diff' | |
alias gb='git branch' | |
# npm/yarn shortcuts - for JavaScript development without the tears | |
alias ni='npm install' | |
alias nid='npm install --save-dev' | |
alias nig='npm install -g' | |
alias nr='npm run' | |
alias nt='npm test' | |
alias yi='yarn install' | |
alias ya='yarn add' | |
alias yad='yarn add --dev' | |
alias yr='yarn run' | |
alias yt='yarn test' | |
# dotnet shortcuts - for when C# calls | |
alias dn='dotnet' | |
alias dnr='dotnet run' | |
alias dnb='dotnet build' | |
alias dnt='dotnet test' | |
alias dnw='dotnet watch' | |
alias dnn='dotnet new' | |
alias dns='dotnet sln' | |
# Python shortcuts - for snake charmers | |
alias py='python' | |
alias pip='pip3' | |
alias pipup='pip install --upgrade pip' | |
alias venv='python -m venv venv' | |
alias activate='source venv/bin/activate' | |
# Custom functions - for the repetitive stuff | |
# Open a file in VSCode or the current directory if no args | |
vs() { | |
if [ $# -eq 0 ]; then | |
code . | |
else | |
code "$@" | |
fi | |
} | |
# Create a new directory and cd into it | |
mkcd() { | |
mkdir -p "$1" && cd "$1" | |
} | |
# Find files and open in VSCode | |
vsc() { | |
local files | |
files=($(fzf --preview 'bat --color=always --style=numbers --line-range=:500 {}' --multi)) | |
[[ -n "$files" ]] && vs "${files[@]}" | |
} | |
# Find command in history and execute it | |
hex() { | |
local cmd | |
cmd=$(atuin search -i) | |
[[ -n "$cmd" ]] && eval "$cmd" | |
} | |
# The magical function that makes VSCode's integrated terminal | |
# inherit all this shell goodness | |
if [[ -n "$VSCODE_INJECTION" ]]; then | |
echo "VSCode Terminal Detected - All systems operational" | |
fi | |
# Node version manager - if you use fnm | |
if command -v fnm &> /dev/null; then | |
eval "$(fnm env --use-on-cd)" | |
fi | |
# Insightful welcome message - because terminal sessions should start with wisdom | |
cat << WELCOME | |
$(tput setaf 2) | |
Terminal Ascension Complete - Welcome to CLI Enlightenment | |
$(tput setaf 3) | |
"In the beginning was the command line." - Neal Stephenson | |
$(tput setaf 6) | |
• Use Ctrl+R to search history with fzf/atuin | |
• Toggle between directory/global history with Ctrl+G | |
• Type 'vsc' to fuzzy-find and open files in VSCode | |
• Type 'hex' to search and execute from your history | |
$(tput sgr0) | |
WELCOME | |
EOF | |
# Configure Zsh as the default shell | |
echo "🐚 Setting Zsh as your default shell..." | |
if [ "$SHELL" != "$(which zsh)" ]; then | |
chsh -s "$(which zsh)" || echo "⚠️ Failed to change default shell. Run 'chsh -s $(which zsh)' manually." | |
else | |
echo "✅ Zsh is already your default shell" | |
fi | |
# Initialize Atuin | |
echo "🧙 Initializing Atuin - importing your command history..." | |
command_exists atuin && { | |
# Import existing shell history | |
atuin import auto || echo "⚠️ Failed to import history. You can try 'atuin import auto' manually." | |
# Offer to set up sync | |
echo "🔄 Would you like to set up Atuin sync to share history across devices? (y/n)" | |
read -r sync_response | |
if [[ "$sync_response" =~ ^[Yy]$ ]]; then | |
echo "📝 Register an Atuin account (or login if you already have one):" | |
atuin register || echo "⚠️ Failed to register. You can try 'atuin register' manually." | |
atuin sync || echo "⚠️ Failed to sync. You can try 'atuin sync' manually." | |
fi | |
} | |
# Configure VSCode if it's installed | |
echo "🧩 Checking for VSCode integration..." | |
if command_exists code; then | |
echo "🔧 Configuring VSCode to use Zsh and FiraCode Nerd Font..." | |
mkdir -p "$HOME/.config/Code/User" | |
VSCODE_SETTINGS="$HOME/.config/Code/User/settings.json" | |
# Backup existing settings | |
if [ -f "$VSCODE_SETTINGS" ]; then | |
cp "$VSCODE_SETTINGS" "$BACKUP_DIR/settings.json" | |
fi | |
# Create or update VSCode settings | |
if [ -f "$VSCODE_SETTINGS" ]; then | |
# Ensure we don't mess up existing JSON - this is a simplified approach | |
# For a real script, consider using jq for proper JSON manipulation | |
grep -q "terminal.integrated.defaultProfile.linux" "$VSCODE_SETTINGS" || { | |
sed -i '$ s/}$/,\n "terminal.integrated.defaultProfile.linux": "zsh",\n "terminal.integrated.fontFamily": "FiraCode Nerd Font"\n}/' "$VSCODE_SETTINGS" | |
} | |
else | |
# Create a new settings file | |
cat > "$VSCODE_SETTINGS" << 'EOF' | |
{ | |
"terminal.integrated.defaultProfile.linux": "zsh", | |
"terminal.integrated.fontFamily": "FiraCode Nerd Font", | |
"editor.fontFamily": "'FiraCode Nerd Font', 'Droid Sans Mono', 'monospace'", | |
"editor.fontLigatures": true | |
} | |
EOF | |
fi | |
echo "✅ VSCode configured to use Zsh and Nerd Fonts" | |
else | |
echo "⚠️ VSCode not detected. If you install it later, update settings manually." | |
fi | |
echo "" | |
echo "🎉 TERMINAL ASCENSION RITUAL COMPLETE 🎉" | |
echo "$(tput setaf 2)Your terminal has been transformed from a digital stone age relic" | |
echo "into a gleaming monument of developer efficiency.$(tput sgr0)" | |
echo "" | |
echo "👉 Next steps:" | |
echo " 1. Log out and log back in, or restart your shell with 'exec zsh'" | |
echo " 2. Run through the Atuin tutorial: 'atuin help'" | |
echo " 3. Customize your Starship prompt: edit ~/.config/starship.toml" | |
echo " 4. Explore your new aliases and functions in ~/.zshrc" | |
echo "" | |
echo "$(tput setaf 3)We've backed up your old configs to: $BACKUP_DIR$(tput sgr0)" | |
echo "Log file created at: $LOG_FILE" | |
echo "" | |
echo "$(tput setaf 6)May your keystrokes be few and your productivity plentiful.$(tput sgr0)" | |
# End of script | |
EOF |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# terminal_descension.sh - The Ritual of Terminal Reversion | |
# For those who tasted CLI enlightenment and decided "nah, I'm good" | |
set -e # Exit immediately if a command exits with a non-zero status | |
echo "================================================================" | |
echo " TERMINAL DESCENSION RITUAL - UNDOING THE ASCENSION " | |
echo " Returning your terminal to its humble origins... " | |
echo "================================================================" | |
# Create a log file for this reversal journey | |
LOG_FILE="$HOME/terminal_descension_log_$(date +%Y%m%d%H%M%S).txt" | |
exec > >(tee -a "$LOG_FILE") 2>&1 | |
echo "Log file: $LOG_FILE" | |
# --- Configuration Restoration --- | |
echo "" | |
echo "🔎 Searching for backup directory created by terminal_ascension.sh..." | |
LATEST_BACKUP=$(find "$HOME" -maxdepth 1 -type d -name '.terminal_backup_*' -printf '%T@ %p\n' | sort -nr | head -n 1 | cut -d' ' -f2-) | |
RESTORED_CONFIG=false | |
if [ -n "$LATEST_BACKUP" ] && [ -d "$LATEST_BACKUP" ]; then | |
echo "✅ Found potential backup directory: $LATEST_BACKUP" | |
echo "🔄 Automatically restoring configuration files from this backup..." | |
# Force 'yes' path | |
echo "🛡️ Restoring configurations..." | |
# Function to restore a backed-up file | |
restore_file() { | |
BACKUP_FILE="$LATEST_BACKUP/$(basename "$1")" | |
if [ -f "$BACKUP_FILE" ]; then | |
echo "Restoring $1 from $BACKUP_FILE" | |
cp "$BACKUP_FILE" "$1" || echo "⚠️ Failed to restore $1" | |
else | |
echo "No backup found for $1 in $LATEST_BACKUP" | |
fi | |
} | |
restore_file "$HOME/.zshrc" | |
restore_file "$HOME/.zpreztorc" # Might not exist if Prezto wasn't fully set up | |
restore_file "$HOME/.config/kitty/kitty.conf" | |
restore_file "$HOME/.config/starship.toml" | |
restore_file "$HOME/.config/Code/User/settings.json" # Restore VSCode settings | |
# Remove Prezto symlinks if they exist, as the backup .zshrc might not use Prezto | |
echo "🧹 Removing potential Prezto symlinks..." | |
find "$HOME" -maxdepth 1 -name '.z*' -lname "${ZDOTDIR:-$HOME}/.zprezto/runcoms/*" -delete || echo "No Prezto symlinks found or error removing them." | |
RESTORED_CONFIG=true | |
echo "✅ Configuration files restored from backup." | |
# Removed else block | |
else | |
echo "⚠️ No backup directory found. Manual restoration might be needed." | |
fi | |
# --- Package Removal --- | |
echo "" | |
echo "🗑️ Preparing to uninstall packages installed by terminal_ascension.sh..." | |
PACKAGES_TO_REMOVE=( | |
"kitty" | |
"zsh" | |
"fzf" | |
# "git" # Usually essential, let's not remove git by default | |
"bat" | |
"exa" | |
"ripgrep" | |
"fd" | |
"gitui" | |
"ttf-firacode-nerd" | |
"atuin" | |
) | |
echo "The following packages might have been installed: ${PACKAGES_TO_REMOVE[*]}" | |
echo "❓ Automatically attempting to uninstall these packages..." | |
# Force 'yes' path | |
echo "Removing packages..." | |
sudo pacman -Rns --noconfirm "${PACKAGES_TO_REMOVE[@]}" || echo "⚠️ Some packages might not have been installed or failed to uninstall." | |
echo "✅ Package removal attempted." | |
# Removed else block | |
# --- Starship Removal --- | |
echo "" | |
echo "🚀 Checking for Starship installation..." | |
STARSHIP_PATH=$(which starship) | |
if [ -n "$STARSHIP_PATH" ]; then | |
echo "Starship binary found at: $STARSHIP_PATH" | |
echo "❓ Automatically removing the Starship binary..." | |
# Force 'yes' path | |
sudo rm -f "$STARSHIP_PATH" || echo "⚠️ Failed to remove Starship binary. Check permissions." | |
echo "✅ Starship binary removal attempted." | |
# Removed else block | |
else | |
echo "Starship binary not found in PATH." | |
fi | |
# --- Remove Cloned Repositories --- | |
echo "" | |
echo "🧹 Checking for cloned repositories..." | |
PREZTO_DIR="${ZDOTDIR:-$HOME}/.zprezto" | |
PDH_DIR="$HOME/per-directory-history" | |
REPOS_TO_REMOVE=() | |
if [ -d "$PREZTO_DIR" ]; then | |
REPOS_TO_REMOVE+=("$PREZTO_DIR") | |
fi | |
if [ -d "$PDH_DIR" ]; then | |
REPOS_TO_REMOVE+=("$PDH_DIR") | |
fi | |
if [ ${#REPOS_TO_REMOVE[@]} -gt 0 ]; then | |
echo "Found the following cloned repositories: ${REPOS_TO_REMOVE[*]}" | |
echo "❓ Automatically removing these directories..." | |
# Force 'yes' path | |
echo "Removing repositories..." | |
rm -rf "${REPOS_TO_REMOVE[@]}" || echo "⚠️ Failed to remove some repositories." | |
echo "✅ Repository removal attempted." | |
# Removed else block | |
else | |
echo "No Prezto or per-directory-history directories found." | |
fi | |
# --- Remove Configuration Files (if not restored from backup) --- | |
echo "" | |
if [ "$RESTORED_CONFIG" = false ]; then | |
echo "🗑️ Configuration files were not restored from backup." | |
CONFIG_FILES_TO_REMOVE=( | |
"$HOME/.config/kitty/kitty.conf" | |
"$HOME/.config/starship.toml" | |
"$HOME/.zshrc" # Remove the one created by the script | |
# "$HOME/.zpreztorc" # This is a symlink, should be gone if Prezto dir removed | |
) | |
echo "The following configuration files might have been created/overwritten: ${CONFIG_FILES_TO_REMOVE[*]}" | |
echo "❓ Automatically removing these specific configuration files..." | |
# Force 'yes' path | |
echo "Removing configuration files..." | |
for file in "${CONFIG_FILES_TO_REMOVE[@]}"; do | |
if [ -f "$file" ]; then | |
rm -f "$file" || echo "⚠️ Failed to remove $file" | |
fi | |
done | |
# Also remove Prezto symlinks again just in case | |
find "$HOME" -maxdepth 1 -name '.z*' -lname "${ZDOTDIR:-$HOME}/.zprezto/runcoms/*" -delete || echo "No Prezto symlinks found or error removing them." | |
echo "✅ Specific configuration file removal attempted." | |
# Removed else block | |
# Offer to revert VSCode settings manually if not restored | |
echo "" | |
echo "⚠️ VSCode settings were not restored from backup." | |
echo " You might need to manually edit $HOME/.config/Code/User/settings.json" | |
echo " to remove 'terminal.integrated.defaultProfile.linux': 'zsh'" | |
echo " and 'terminal.integrated.fontFamily': 'FiraCode Nerd Font'." | |
fi | |
# --- Change Default Shell --- | |
echo "" | |
echo "🐚 Checking default shell..." | |
CURRENT_SHELL=$(getent passwd "$USER" | cut -d: -f7) | |
ZSH_PATH=$(which zsh) | |
if [ "$CURRENT_SHELL" == "$ZSH_PATH" ]; then | |
echo "Your current default shell is Zsh." | |
echo "❓ Automatically changing the default shell back to /bin/bash..." | |
# Force 'yes' path | |
echo "Changing default shell to /bin/bash..." | |
sudo chsh -s /bin/bash "$USER" || echo "⚠️ Failed to change default shell. Run 'sudo chsh -s /bin/bash $USER' manually." | |
echo "✅ Default shell change attempted. You may need to log out and back in." | |
# Removed else block | |
else | |
echo "Your default shell is not Zsh ($CURRENT_SHELL)." | |
fi | |
# --- Atuin Data Removal (Optional and Dangerous) --- | |
echo "" | |
echo "⚠️ Atuin stores its database and configuration typically in ~/.local/share/atuin/ and ~/.config/atuin/" | |
echo " Removing this will permanently delete your synchronized command history if you used sync." | |
echo "❓ Automatically removing the Atuin data directories... (EXTREME CAUTION ADVISED)" | |
# Force 'yes' path | |
echo "Removing Atuin data directories..." | |
rm -rf "$HOME/.local/share/atuin" "$HOME/.config/atuin" || echo "⚠️ Failed to remove Atuin directories." | |
echo "✅ Atuin data removal attempted." | |
# Removed else block | |
# --- Final Messages --- | |
echo "" | |
echo "🎉 TERMINAL DESCENSION RITUAL COMPLETE 🎉" | |
echo "$(tput setaf 1)The terminal has been returned to a more... conventional state.$(tput sgr0)" | |
echo "" | |
echo "👉 Important Notes:" | |
echo " * You may need to log out and log back in for shell changes to take full effect." | |
echo " * If you skipped restoring backups, your original config files might still be in a backup directory like '$LATEST_BACKUP'." | |
echo " * Some packages might have dependencies that were not removed. You can run 'sudo pacman -Qdt' to list orphans and 'sudo pacman -Rns \$(pacman -Qdtq)' to remove them (use with care)." | |
echo " * Review the log file for details: $LOG_FILE" | |
echo "" | |
echo "$(tput setaf 6)May your default terminal serve you adequately.$(tput sgr0)" | |
# End of script |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment